ホームページ > データベース > mysql チュートリアル > JDBC - データ接続プールの使用

JDBC - データ接続プールの使用

黄舟
リリース: 2017-02-11 11:07:12
オリジナル
1067 人が閲覧しました


データベース接続プール

昨日の演習では、各演習でデータベースとの接続を確立し、完了したら切断する必要がありましたが、処理されるデータ量が特殊な場合、非常に時間がかかります。今日は、接続プールの使用法、接続プールへの接続の入れ方、および必要に応じて接続を取り出す方法を学習します。使用後に接続をプールに戻すことは、接続を切断することを意味しません。

データベース接続プールの基本的な考え方は、データベース接続用の「バッファー プール」を確立することです。データベース接続を確立する必要がある場合、あらかじめ一定数のコネクションをバッファプールに入れておくと、使用後に「バッファプール」から 1 つだけ取り出して元に戻すだけで済みます。
データベース接続プールは、初期化中に特定の数のデータベース接続を作成し、それらを接続プールに入れます。これらのデータベース接続の数は、データベース接続の最小数によって設定されます。これらのデータベース接続が使用されるかどうかに関係なく、接続プールには少なくともこの数の接続が常に保証されます。接続プール内のデータベース接続の最大数は、接続プールが占有できる最大接続数を制限します。アプリケーションによって接続プールから要求された接続数が最大接続数を超えると、これらの要求は接続プールに追加されます。待機列。
従来の開発におけるデータベース接続の問題を解決するために、データベース接続プール技術を使用できます。
データベース接続プールは、データベース接続の割り当て、管理、解放を担当します。これにより、アプリケーションはデータベース接続を再確立するのではなく、既存のデータベース接続を再利用できるようになります。

DBCP 接続プール

まず、DBCP 接続プール (無料のオープンソース接続プール) を使用します。まず、commons-dbcp-1.4.jar ファイルを現在のプロジェクトに配置し、環境を構成する必要があります (ビルドに追加)。パス)。プログラムを通じて DBCP 接続プールを使用する方法を学びましょう:


ここで前と同様に「dbcp.properties」ファイルを作成し、必要なパラメータをその中に入れる必要があります。その内容は次のとおりです (このファイルは配置されます)。現在のプロジェクトでは)、DBCP 接続プールはこのファイルを使用して mysql および oracle 接続プールの確立を完了できますが、一度に作成できるのは 1 つだけであり、もう 1 つはコメントする必要があります。

driverClassName = com.mysql.jdbc.Driver

url = jdbc:mysql://127.0.0.1:3306/company

username = root

password = 123456


初期サイズ = 5

maxActive = 50

maxIdle = 10


#driverClassName = oracle.jdbc.driver.OracleDriver

#url = jdbc:oracle:thin:@127.0.0.1:1521:orcl

#username = scオット

#password = Tiger


package com.atguigu.jdbc;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.junit.Test;

public class DBCPTest {
	@Test
	public void test2() throws Exception {
		Properties properties = new Properties();
		properties.load(new FileInputStream("dbcp.properties"));
		DataSource dataSource = BasicDataSourceFactory.createDataSource(properties);
		System.out.println("inital:" + ((BasicDataSource)dataSource).getInitialSize());
		System.out.println("getMaxActive:" + ((BasicDataSource)dataSource).getMaxActive());
		System.out.println("getMaxIdle:" + ((BasicDataSource)dataSource).getMaxIdle());
		Connection connection = dataSource.getConnection();
		System.out.println(connection);
		connection.close();
	}
}
ログイン後にコピー


C3P0接続プール

次に、上で行ったように、より強力な接続プール、C3P0 (まだ無料でオープンソースの接続プール) について学習します。まず、commons-dbcp-1.4.jar ファイルを現在のプロジェクトに配置し、環境を構成する (ビルド パスに追加する) 必要があります。

DBCP 接続プールの使用方法を学習するプログラムは次のとおりです。

DBCP 接続プールと同様に、「c3p0-config.xml」ファイルを作成し、その中に必要なパラメーターを追加する必要があります。その内容は次のとおりです。 (このファイルは現在のプロジェクトの src ディレクトリに置かれます)



com.mysql.jdbc.Driver
jdbc:mysql://127.0.0.1:3306/school
root
123456
5
5
5
50
0
5
oracle.jdbc.driver.OracleDriver
jdbc:mysql://127.0.0.1:3306/school
root
123456

DBCP连接池使用这个文件可以完成mysql、oracle的连接池的建立,每次只能建立一个,但是另一个需要注释起来。因为我们是根据 名建立连接,


package com.atguigu.jdbc;

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.junit.Test;

import com.mchange.v2.c3p0.*;

public class C3P0Test {
	@Test
	public void test1() throws PropertyVetoException, SQLException {
		DataSource dataSource = new ComboPooledDataSource("mysql-config"); // 它会默认自动去读取文件
		System.out.println(dataSource);
		Connection connection = dataSource.getConnection();
		System.out.println(connection);
		connection.close();// 把连接归还给连接池
		
		DataSources.destroy(dataSource);// 完全释放池中所有连接,并销毁连接池!!
	}		
		
	@Test
	public void test2() throws PropertyVetoException, SQLException {
		DataSource dataSource = new ComboPooledDataSource("oracle-config"); // 它会默认自动去读取文件
		System.out.println(dataSource);
		Connection connection = dataSource.getConnection();
		System.out.println(connection);
		connection.close();// 把连接归还给连接池
		
		DataSources.destroy(dataSource);// 完全释放池中所有连接,并销毁连接池!!
	}	
}
ログイン後にコピー


学习了连接池之后,JdbcUtil工具类中的getConnection方法就可以应用,如下:


package com.atguigu.jdbc;

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

import javax.sql.DataSource;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.mchange.v2.c3p0.DataSources;

/**
 * 预备工作 : 
 * 	1) 把要访问的数据库相关的驱动程序复制到项目中, 就是jar包
 * 	2) 配置项目属性, 把jar包导入到本项目的buildpath中
 * @author Administrator
 *
 */
public class JdbcUtil {
	
	private static DataSource dataSource; // 声明静态属性对象引用.
	
	static {
		dataSource = new ComboPooledDataSource("mysql-config"); // 连接池对象只需要创建一次就可以了
	}
	
	public static Connection getConnection() throws SQLException {
		return dataSource.getConnection(); // 要想获取连接, 只需要从连接池中获取,用完以后, 再归还回来
	}
	
	public static Connection getConnectionOld() throws IOException, ClassNotFoundException, SQLException {
		// 1) 读取配置文件
		Properties properties = new Properties();
		properties.load(new FileInputStream("jdbc.properties"));
		// 2) 获取配置文件中的必要的信息
		String driverClass = properties.getProperty("driverClass");
		String url = properties.getProperty("url");
		String user = properties.getProperty("user");
		String password = properties.getProperty("password");
		// 3) 注册驱动 , 加载驱动类
		Class.forName(driverClass);
		// 4) 通过驱动管理器获取连接(需要url,用户,密码)
		return DriverManager.getConnection(url, user, password);// 暗含  new Socket(host,port), 认证,其他各种初始化操作
	}
	
	//关闭连接
	public static void close(Connection connection) {
		close(connection, null);
	}
	
	public static void close(Connection connection, Statement statement) {
		close(connection, statement, null);
	}
	
	public static void close(Connection connection, Statement statement, ResultSet resultSet) {
		if (resultSet != null) {
			try {
				resultSet.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		if (statement != null) {
			try {
				statement.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		if (connection != null) {
			try {
				connection.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	//销毁连接池
	public static void destroy() {
		try {
			DataSources.destroy(dataSource);
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}	
}
ログイン後にコピー


DBUtils工具类

将常用的操作数据库的JDBC的类和方法集合在一起,就是DBUtils.JDBC。提供供我们使用的工具类QueryRunner来执行操作。
在使用之前我们仍然需要将commons-dbutils-1.3.jar添加到当前工程下,并添加到path路径。

package com.atguigu.jdbc;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ArrayListHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.junit.Test;

public class QueryRunnerTest {

	// 使用我们自定义工具实现表的创建
	@Test
	public void test1() throws SQLException {
		QueryRunner qr = new QueryRunner();
		Connection connection = JdbcUtil.getConnection();
		qr.update(connection, "create table test2(aa int, bb varchar(10))");
		JdbcUtil.close(connection);
	}	
	
	// 使用我们自定义工具向表中插入一条记录
	@Test
	public void test2() throws SQLException {
		QueryRunner qr = new QueryRunner();
		Connection connection = JdbcUtil.getConnection();
		int rows = qr.update(connection, "insert into test2(aa, bb) values(?,?)", 10, "xxx");
		System.out.println(rows + " rows");
		JdbcUtil.close(connection);
	}
	
	// 使用DBUtils.JDBC接口中提供的方法对departments表进行查询,把结果集中的所有记录转换为department对象集合并存入List集合中,然后遍历输出对象
	@Test
	public void test3() throws SQLException {
		//query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) 
		String sql = "select * from departments where department_id > ?";
		QueryRunner qr = new QueryRunner();
		Connection connection = JdbcUtil.getConnection();
		BeanListHandler<Department> rsh = new BeanListHandler<Department>(Department.class); // 把结果集中的所有记录转换为对象集合
		List<Department> list = qr.query(connection, sql, rsh, 20);
		for (Department department : list) {
			System.out.println(department);
		}
	}	
	
	// 使用DBUtils.JDBC接口中提供的方法对departments表进行查询,把结果集中的一条记录转换为department实体对象,然后输出对象
	@Test
	public void test4() throws SQLException {
		String sql = "select * from departments where department_id = ?";
		QueryRunner qr = new QueryRunner();
		Connection connection = JdbcUtil.getConnection();
		BeanHandler<Department> rsh = new BeanHandler<Department>(Department.class); // 把结果集中的一条记录转换为实体对象
		Department objDepartment = qr.query(connection, sql, rsh, 20);
		System.out.println(objDepartment);
	}
	
	// 使用DBUtils.JDBC接口中提供的方法对departments表进行查询,将每一条记录存入集合中,然后遍历输出每一个数据
	@Test
	public void test5() throws SQLException {
		String sql = "select * from employees";
		QueryRunner qr = new QueryRunner();
		Connection connection = JdbcUtil.getConnection();
		ArrayListHandler rsh = new ArrayListHandler();
		List<Object[]> list = qr.query(connection, sql, rsh);
		for (Object[] objects : list) {
			for (int i = 0; i < objects.length; i++) {
				System.out.print(objects[i] + "\t");
			}
			System.out.println();
		}
	}		
		
	// 使用DBUtils.JDBC接口中提供的方法对departments表进行查询,将查询到的一个数据输出	
	@Test
	public void test6 () throws SQLException {
		String sql = "select count(*) from world.country";
		QueryRunner qr = new QueryRunner();
		Connection connection = JdbcUtil.getConnection();
		ScalarHandler rsh = new ScalarHandler();
		Object singleValue = qr.query(connection, sql, rsh);
		System.out.println(singleValue);
	}	
	
		
	@Test
	public void test7() throws Exception {
		QueryRunner qr = new QueryRunner();
		List<Object> list = qr.query(JdbcUtil.getConnection(),  "select * from student", new ColumnListHandler(1));
		for (Object object : list) {
			System.out.println(object);
		}
	}

	//MapHandler把第一行数据封装到Map集合中, 列名作为键, 对应值作为值
	@Test
	public void test8() throws Exception {
		QueryRunner qr = new QueryRunner();
		Map<String, Object> map = qr.query(JdbcUtil.getConnection(),  "select * from student", new MapHandler());
		Set<String> keys = map.keySet();
		for (String key : keys) {
			Object value = map.get(key);
			System.out.println(key + " -------- " + value);
		}
	}	
		
	//MapListHandler把一行数据封装到Map集合中, 并把所有行生成的Map再放入一个List集合
	@Test
	public void test9() throws Exception {
		QueryRunner qr = new QueryRunner();
		List<Map<String, Object>> list = qr.query(JdbcUtil.getConnection(),  "select * from student", new MapListHandler());
		for (Map<String, Object> map2 : list) {
			Set<String> keys = map2.keySet();
			for (String key : keys) {
				Object value = map2.get(key);
				System.out.println(key + " -------- " + value);
			}
			System.out.println();
		}
	}	
}
ログイン後にコピー

到这里就可以统一整理一下自己定义的JdbcUtil工具类、CommonUtil工具类,使自定义的工具类能达到JDButi.JDBC相同的功能,如下:

JdbcUtils.java


package com.atguigu.jdbc;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.sql.DataSource;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.mchange.v2.c3p0.DataSources;

public class JdbcUtil {
	
	private static DataSource dataSource;
	
	static {
		dataSource = new ComboPooledDataSource("config1"); // 它必须依赖文件src/c3p0-config.xml
	}
	
	// 获取c3p0连接池的连接
	public static Connection getConnection() throws SQLException {
		return dataSource.getConnection();
	}
	
	public static void close(Connection connection) {
		close(connection, null);
	}
	
	public static void close(Connection connection, Statement statement) {
		close(connection, statement, null);
	}
	
	public static void close(Connection connection, Statement statement, ResultSet resultSet) {
		if (resultSet != null) {
			try {
				resultSet.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		if (statement != null) {
			try {
				statement.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		if (connection != null) {
			try {
				connection.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void destroy() {
		try {
			DataSources.destroy(dataSource);
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}
ログイン後にコピー


CommonUtil.java

package com.atguigu.jdbc;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class CommonUtil {
	
	/**
	 * 把结果集中的每一行都放入Object对象数组中, 再把所有的Object对象数组放入一个List集合中.
	 * @throws SQLException 
	 */
	public static List<Object[]> query(Connection connection, String sql, Object... values) throws SQLException {
		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		try {
			preparedStatement = connection.prepareStatement(sql);
			fillArguments(preparedStatement, values);
			resultSet = preparedStatement.executeQuery();
			List<Object[]> list = new ArrayList<Object[]>();
			int cols = resultSet.getMetaData().getColumnCount();
			while (resultSet.next()) {
				Object[] dataRow = new Object[cols];
				for (int i = 0; i < dataRow.length; i++) {
					dataRow[i] = resultSet.getObject(i + 1);
				}
				list.add(dataRow);
			}
			return list;
		} finally {
			JdbcUtil.close(null, preparedStatement, resultSet);
		}
	}

	/**
	 * 把结果集中的第一行数据,全放入一个对象数组中
	 * @throws SQLException 
	 */
	public static Object[] queryValueArray(Connection connection, String sql, Object... values) throws SQLException {
		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		try {
			preparedStatement = connection.prepareStatement(sql);
			fillArguments(preparedStatement, values);
			resultSet = preparedStatement.executeQuery();
			if (resultSet.next()) {
				Object[] dataRow = new Object[resultSet.getMetaData().getColumnCount()];
				for (int i = 0; i < dataRow.length; i++) {
					dataRow[i] = resultSet.getObject(i + 1);
				}
				return dataRow;
			} else {
				return null;
			}
		} finally {
			JdbcUtil.close(null, preparedStatement, resultSet);
		}
	}
	
	/**
	 * 从结果集中获取第一行的第一列
	 * @throws SQLException 
	 */
	public static Object queryValue(Connection connection, String sql, Object... values) throws SQLException {
		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		try {
			preparedStatement = connection.prepareStatement(sql);
			fillArguments(preparedStatement, values);
			resultSet = preparedStatement.executeQuery();
			if (resultSet.next()) {
				return resultSet.getObject(1);
			} else {
				return null;
			}
		} finally {
			JdbcUtil.close(null, preparedStatement, resultSet);
		}
	}
	
	/**
	 * 把结果集中第一行转换为对象返回
	 * @throws SQLException 
	 * @throws SecurityException 
	 * @throws NoSuchFieldException 
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 */
	public static <T> T queryBean(Connection connection, String sql, Class<T> clazz, Object... values) throws SQLException, 
	NoSuchFieldException, SecurityException, InstantiationException, IllegalAccessException {
		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		try {
			preparedStatement = connection.prepareStatement(sql);
			
			fillArguments(preparedStatement, values);
			
			resultSet = preparedStatement.executeQuery();
			if (resultSet.next()) {
				T t = clazz.newInstance();
				ResultSetMetaData metaData = resultSet.getMetaData();
				int cols = metaData.getColumnCount();
				for (int i = 0; i < cols; i++) {
					String label = metaData.getColumnLabel(i + 1);
					Object value = resultSet.getObject(label);
					Field field = clazz.getDeclaredField(label);
					field.setAccessible(true);
					field.set(t, value);
				}
				return t;
			} else {
				return null;
			}
		} finally {
			JdbcUtil.close(null, preparedStatement, resultSet);
		}
	}

	/**
	 * 把结果集的所有记录都封装成对象,并把所有对象放在一个List集合中
	 * @throws SQLException 
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 * @throws SecurityException 
	 * @throws NoSuchFieldException 
	 */
	public static <T> List<T> query(Connection connection, String sql, Class<T> clazz, Object... values) throws SQLException, 
	InstantiationException, IllegalAccessException, NoSuchFieldException, SecurityException {
		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		try {
			preparedStatement = connection.prepareStatement(sql);
			
			fillArguments(preparedStatement, values);
			
			resultSet = preparedStatement.executeQuery();
			List<T> list = new ArrayList<T>();
			ResultSetMetaData metaData = resultSet.getMetaData();
			int cols = metaData.getColumnCount();
			while (resultSet.next()) {
				T t = clazz.newInstance();
				for (int i = 0; i < cols; i++) {
					String label = metaData.getColumnLabel(i + 1);
					Object value = resultSet.getObject(label);
					if (value != null) {
						Field field = clazz.getDeclaredField(label);
						field.setAccessible(true);
						field.set(t, value);
					}
				}
				list.add(t);
			}
			return list;
		} finally {
			JdbcUtil.close(null, preparedStatement, resultSet);
		}
	}
	
	/**
	 * 通用更新操作
	 * @throws SQLException 
	 */
	public static int update(Connection connection, String sql, Object... values) throws SQLException {
		PreparedStatement preparedStatement = null;
		try {
			preparedStatement = connection.prepareStatement(sql);
			fillArguments(preparedStatement, values);
			return preparedStatement.executeUpdate();
		} finally {
			JdbcUtil.close(null, preparedStatement);
		}
	}
	
	public static void fillArguments(PreparedStatement preparedStatement, Object... values) throws SQLException {
		for (int i = 0; i < values.length; i++) {
			preparedStatement.setObject(i + 1, values[i]);
		}
	}	
}
ログイン後にコピー

BaseDAO

综合之前学习过的知识,在这里创建一个BaseDAO类借助DBUtils工具类实现数据操作功能:


package com.atguigu.jdbc;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

public class BaseDAO<T>{

	protected Class<T> clazz; // T泛型究竟是什么类型, 用类模板对象来描述
	protected QueryRunner qr = new QueryRunner(); // 用于执行通用查询和更新的工具类对象
	protected Connection connection; // 数据库连接
	protected String tableName; // 涉及到的表,需要通过构造器初始化赋值
	
	public JdbcDAO(String tableName) {
		// 以下代码的执行者是子类对象,所以this.getClass是获取子类的类模板对象
		Type type = this.getClass().getGenericSuperclass(); // JdbcDAO<Teacher>
		if (type instanceof ParameterizedType) {
			ParameterizedType parameterizedType = (ParameterizedType)type;//JdbcDAO<Teacher>
			Type[] types = parameterizedType.getActualTypeArguments();
			clazz = (Class<T>)types[0];
		} else {
			clazz = (Class<T>)Object.class;
		}
		// 获取一个连接供所有方法使用
		try {
			connection = JdbcUtil.getConnection();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		this.tableName = tableName;
	}
	
	//获得记录中具体的一个数据
	public Object getValue(String sql, Object... values) {
		try {
			return qr.query(connection, sql, new ScalarHandler(), values);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return null;
	}

	//获得一行数据并封装成javabean对象
	public T get(String sql, Object... values) {
		try {
			return qr.query(connection, sql, new BeanHandler<T>(clazz), values);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return null;
	}

	//获得多行记录,封装成javabean对象,保存在list集合中
	public List<T> getList(String sql, Object... values) {
		try {
			return qr.query(connection, sql, new BeanListHandler<T>(clazz), values);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	
	//获得所有记录,封装成javabean对象,保存在list集合中
	public List<T> getAll() {
		return getList("select * from " + tableName);
	}
	
	//根据id获取某一条记录,并封装成javabean对象返回
	public T getById(int id) {
		return get("select * from " + tableName + " where id = ?", id);
	}
	
	//根据id删除某一条记录,删除成功返回ture,失败返回false
	public boolean deleteById(int id) {
		int rows = update("delete from " + tableName + " where id = ?", id);
		if (rows > 0) {
			return true;
		}
		return false;
	}

	//通用的更新操作
	public int update(String sql, Object... values) {
		try {
			return qr.update(connection, sql, values);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return 0;
	}
	
	//关闭连接
	public void close() {
		JdbcUtil.close(connection);
	}
}
ログイン後にコピー


以上就是JDBC-数据连接池的使用 的内容,更多相关内容请关注PHP中文网(m.sbmmt.com)!







    

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート