Java 基础 27~ 使用 JDBC+ 连接池,这篇文章可以满足你 80% 日常工作
ex.printStackTrace();
}
}
}
Statement 的子接口
优点:对 SQL 语句进行了预编译,数据库可以直接执行,更加高效和安全。
创建方法:
Connection 对象.prepareStatement("SQL 语句");
常用方法:
| 方法名 | 说明 |
| --- | --- |
| setXXX(占位符位置,值) | 给占位符赋值,如:setInt、setString、setFloat… |
| ResultSet executeQuery() | 执行查询 |
| int executeUpdate() | 执行增删改 |
当需要操作大量数据时,默认情况下 JDBC 会单独编译和发送每一条 SQL 语句,执行效率比较低。
批处理:将多条语句打包,一起编译,一起发送给数据库,数据库一起执行。
实现方法:
需要关闭连接对象的自动提交
创建 PreparedStatement 对象
设置 SQL 语句和占位符
调用 PreparedStatement 的 addBatch 方法,添加 SQL 命令到批处理中
调用 executeBatch()执行批处理
调用连接对象的 commit 方法
/**
测试批处理
*/
public class TestBatch {
public static final String URL = "jdbc:mysql://localhost:3306/mydb?user=root&password=123456";
static{
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//添加多个学生的信息
public void addStudents(List<Student> students){
try(Connection conn = DriverManager.getConnection(URL)){
//关闭自动提交
conn.setAutoCommit(false);
String sql = "insert into students(stu_name,stu_age,stu_gender,stu_class_id) values(?,?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
for(Student stu : students){
//添加一个学生的信息
ps.setString(1, stu.getStu_name());
ps.setInt(2,stu.getStu_age());
ps.setString(3, stu.getStu_gender());
ps.setInt(4, stu.getStu_class_id());
//添加到批处理
ps.addBatch();
}
//执行批处理
ps.executeBatch();
//数据库提交
conn.commit();
}catch(SQLException ex){
ex.printStackTrace();
}
}
@Test
public void testBatch(){
List<Student> students = Arrays.asList(new Student(1,"马八",20,"男",1),
new Student(1,"马大八",30,"男",1),new Student(1,"马小八",10,"男",1));
addStudents(students);
}
}
====================================================================
创建数据库连接对象需要消耗比较多时间和内存,连接池开辟一个池,在池中放置一定数量的连接对象,用户使用连接对象后,连接不会直接销毁,而是回到池中,做其它操作时可以直接利用,减少连接对象的创建次数,从而提高程序的性能。
C3p0
开源的,成熟的,高并发第三方数据库连接池,文档资料完善,hibernate 框架就使用了 c3p0
dbcp
由 Apache 开发的一个数据库连接池,在 tomcat7 版本之前都是使用 dbcp 作为数据库连接池。
Druid
阿里巴巴的连接池。Druid 能够提供强大的监控和扩展功能。
BoneCP
其官方说该数据库连接池性能非常棒,不过现在已经不更新了,转到了 HiKariCP 上。
HiKariCP
Hikari 是日语光的意思,作者可能想以此来表达 HiKariCP 速度之快。比之前的 BoneCP 性能更加强大,它官方展示了一些性能对比的数据,通过数据可以看出 HiKariCP 完虐 c3p0,dbcp,tomcat jdbc pool 等其他数据库连接池。并且它的库文件差不多就 130kb,非常轻巧。
Proxool
早期的一些项目中使用的多一些,现在该数据库连接池源码已经有一阵子不更新了。
步骤
从官网下载 jar 包 http://www.mchange.com/projects/c3p0/index.html
c3p0-0.9.5.2.jar
mchange-commons-java-0.2.11.jar
创建 ComboPooledDataSource 对象
配置连接池对象
调用 getConnection 获得连接
执行 CRUD 操作
关闭连接,让连接回到池中
配置文件
src 下添加:c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb</property>
<property name="user">root</property>
<property name="password">123456</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
<property name="maxStatements">200</property>
</default-config>
</c3p0-config>
案例:使用 JDBC+连接池+反射编写基本的 ORM 框架
常见的 ORM(对象关系映射)框架,如 Hibernate、MyBatis 能通过对 Java 对象的操作,完成对数据库的增删改查,下面模拟其中的保存和查询操作。
保存对象:
通用的 save 方法
参数是 Java 对象
可以向任何表插入一条数据
问题:
不同表的 save 方法,参数类型不一样
insert 语句是不一样的
分析:
将参数设置为 Object
将实体类名、属性名设置成和表名、字段名一致
通过反射读取对象的类名,属性名,动态拼接 SQL 语句
调用 getXXX 方法,将值设置到 SQL 语句中
/**
JDBC 工具类
*/
public class JDBCUtils {
//连接池对象
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
/**
保存任意对象的数据到表中
@param obj
*/
public static void save(Object obj){
//获得对象的类型对象
Class clazz = obj.getClass();
//获得类名
String className = clazz.getSimpleName();
StringBuilder stb = new StringBuilder("insert into ");
stb.append(className+"(");
//获得所有的属性
Field[] fields = clazz.getDeclaredFields();
//i = 1 跳过第一列自动增长主键
for(int i = 1;i < fields.length;i++){
stb.append(fields[i].getName()+",");
}
//删除最后的,
stb.deleteCharAt(stb.length() - 1);
stb.append(") values (");
//添加?
for(int i = 1;i < fields.length;i++){
stb.append("?,");
}
//删除最后的,
stb.deleteCharAt(stb.length() - 1);
stb.append(")");
System.out.println("test sql--- "+stb.toString());
//添加数据
try(Connection conn = dataSource.getConnection()){
PreparedStatement ps = conn.prepareStatement(stb.toString());
//设置 SQL 语句中的参数
for(int i = 1;i < fields.length;i++){
String fname = fields[i].getName();
String mname = "get" + fname.substring(0, 1).toUpperCase() + fname.substring(1);
System.out.println("method:"+mname);
//调用方法获得返回值,给 SQL 参数赋值
Method m = clazz.getMethod(mname);
ps.setObject(i, m.invoke(obj));
}
//执行 SQL
ps.executeUpdate();
System.out.println("添加完成!!!");
}catch(Exception ex){
ex.printStackTrace();
}
}
}
查询操作,查询不同的表,返回不同类型的集合
问题:
表的名称不同
返回值类型不同
分析:
使用泛型方法,通过 T 来代替返回值的类型,类型由 Class 参数传入
通过反射机制创建对象,给对象的属性赋值
前提是实体类的属性名和表的字段名一致
/**
数据库查询,获得对象集合
*/
public static <T> List<T> query(Class<T> clazz, String sql, Object... args) throws Exception {
List<T> list = new ArrayList<>();
//获得连接
Connection connection = dataSource.getConnection();
//获得命令对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//设置占位符参数值
for(int i = 0;i < args.length;i++){
preparedStatement.setObject(i + 1,args[i]);
}
//查询每一行
ResultSet resultSet = preparedStatement.executeQuery();
while(resultSe
t.next()){
//创建对象
T obj = clazz.newInstance();
//获得所有属性
Field[] fields = clazz.getDeclaredFields();
//遍历属性
for(Field field : fields){
String name = field.getName();
//获得属性对应列的值
Object value = resultSet.getObject(name);
field.setAccessible(true);
//给对象属性赋值
field.set(obj,cast(field.getType(),value));
}
list.add(obj);
}
return list;
}
评论