Java基础27~使用JDBC+连接池+反射编写简单的ORM框架

为热爱编程的你点赞!
学习SpringBoot实战课程 https://edu.csdn.net/course/detail/31433
学习SpringCloud入门课程 https://edu.csdn.net/course/detail/31451


前言

项目开发离不开数据库,JDBC是Java数据库开发必须要掌握的技术。

JDBC概述

JDBC(Java Database Connectivity,Java数据库连接)作用是:连接数据库,对表中的数据进行增删改查操作

JDBC的API

在java.sql包下

类名 说明
DriverManager 驱动管理器类,管理数据库驱动,获得数据库连接
Connection 连接类,连接数据库
Statement 命令接口,发送SQL命令给数据库
PreparedStatement 预编译命令接口,命令接口的子接口
ResultSet 结果集,保存查询的数据

DriverManager类

用于管理驱动,并获得连接对象:

  • Connection getConnection(URL字符串)
  • Connection getConnection(URL字符串,账号,密码)

URL字符串写法:

jdbc:mysql://数据库服务器地址:3306/数据库名?参数=值&参数=值

URL参数:

参数名 说明
user 账号
password 密码
useSSL 是否加密传输 true/false
useUnicode 是否使用Unicode字符 true/false
characterEncoding 指定编码类型
serverTimezone 时区

Connection接口

通过TCP/IP协议实现和数据库的通信,实现连接
常用方法:

方法名 说明
createStatement() 创建Statement对象
prepareStatement(String sql) 创建PreparedStatement对象
setAutocommit() 设置是否自动提交
beginTransaction() 启动事务
commit() 提交事务
rollback() 回滚事务
close() 关闭连接

Statment接口

用于向数据库发送SQL命令
常用方法:

方法名 说明
ResultSet executeQuery(SQL语句) 执行查询语句
int executeUpdate(SQL语句) 执行增删改语句
close() 关闭命令

ResultSet接口

用于查询数据
常用方法:

方法名 说明
boolean next() 移动到下一行,返回是否到了末尾
boolean first() 移动第一行,返回是否有数据
boolean last() 移动到最后一行,返回是否有数据
String getString(“列名” 或 列索引) 获得某一列的字符串值
int getInt(“列名” 或 列索引) 获得某一列的整数值

JDBC的操作步骤

  1. 下载mysql驱动包,导入到项目中
  2. 导入驱动包中的Driver类到内存 Class.forName(“包名+类名”);
  3. 通过DriverManager获得Connection对象
  4. 通过Connection对象获得Statement对象
  5. Statement对象执行增删改查命令
  6. 如果是查询,获得ResultSet对象
  7. 遍历ResultSet对象中的数据
/**
 * 测试查询功能
 */
public class TestQuery {

	public static final String URL = "jdbc:mysql://localhost:3306/mydb";
	
	static{
		try {
			Class.forName("com.mysql.jdbc.Driver");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	@Test
	public void testJDBC(){
		try (Connection conn = DriverManager.getConnection(URL,"root","123456")){
			Statement statement = conn.createStatement();
			String sql = "insert into students(stu_name,stu_age,stu_gender,stu_class_id) values(‘张三’,20,‘男’,1)";
			int rows = statement.executeUpdate(sql);
			if(rows > 0){
				System.out.println("执行成功");
			}else{
				System.out.println("执行失败");
			}
			statement.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	@Test
	public void testQuery(){
		try(Connection conn = DriverManager.getConnection(URL,"root","123456")){
			Statement st = conn.createStatement();
			String sql = "select * from students";
			//执行查询操作,返回结果集
			ResultSet rs = st.executeQuery(sql);
			//循环访问每一行
			while(rs.next()){
				//访问每一列
				System.out.println("编号:"+rs.getInt("stu_id"));
				System.out.println("姓名:"+rs.getString("stu_name"));
				System.out.println("年龄:"+rs.getInt("stu_age"));
				System.out.println("性别:"+rs.getString("stu_gender"));
				System.out.println("班级:"+rs.getInt("stu_class_id"));
			}
			rs.close();
		}catch(SQLException ex){
			ex.printStackTrace();
		}
	}
}

PreparedStatement接口

Statement的子接口
优点:对SQL语句进行了预编译,数据库可以直接执行,更加高效和安全。
创建方法:

Connection对象.prepareStatement("SQL语句");

常用方法:

方法名 说明
setXXX(占位符位置,值) 给占位符赋值,如:setInt、setString、setFloat…
ResultSet executeQuery() 执行查询
int executeUpdate() 执行增删改

批处理

当需要操作大量数据时,默认情况下JDBC会单独编译和发送每一条SQL语句,执行效率比较低。
批处理:将多条语句打包,一起编译,一起发送给数据库,数据库一起执行。
实现方法:

  1. 需要关闭连接对象的自动提交
  2. 创建PreparedStatement对象
  3. 设置SQL语句和占位符
  4. 调用PreparedStatement的addBatch方法,添加SQL命令到批处理中
  5. 调用executeBatch()执行批处理
  6. 调用连接对象的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 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 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
    早期的一些项目中使用的多一些,现在该数据库连接池源码已经有一阵子不更新了。

C3P0连接池的使用

步骤

  1. 从官网下载jar包 http://www.mchange.com/projects/c3p0/index.html
    c3p0-0.9.5.2.jar
    mchange-commons-java-0.2.11.jar
  2. 创建ComboPooledDataSource对象
  3. 配置连接池对象
  4. 调用getConnection获得连接
  5. 执行CRUD操作
  6. 关闭连接,让连接回到池中

配置文件
src下添加:c3p0-config.xml



  
  	
  	
  	com.mysql.jdbc.Driver
  	
  	jdbc:mysql://localhost:3306/mydb
  	
  	root
  	
  	123456
  	
  	
    10
    
    30
    
    100
    
    10
    
    200
  

案例:使用JDBC+连接池+反射编写基本的ORM框架

常见的ORM(对象关系映射)框架,如Hibernate、MyBatis能通过对Java对象的操作,完成对数据库的增删改查,下面模拟其中的保存操作。
需求:

  1. 通用的save方法
  2. 参数是Java对象
  3. 可以向任何表插入一条数据

问题:

  1. 不同表的save方法,参数类型不一样
  2. insert语句是不一样的

分析:

  1. 将参数设置为Object
  2. 将实体类名、属性名设置成和表名、字段名一致
  3. 通过反射读取对象的类名,属性名,动态拼接SQL语句
  4. 调用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();
		}
	}
}

结束

作业:定义findAll方法,查询不同的表,返回不同类型的集合
问题:

  1. 表的名称不同
  2. 返回值类型不同

分析:

  1. 给findAll传Class参数,通过getSimpleName获得类名,就是表名
  2. 使用泛型方法,通过T来代替类型,具体类型由Class参数传入
public static  List findAll(String sql,Class clazz){

}
  1. 使用反射实现SQL拼接
  2. 从结果集中读取数据后,动态创建对象,调用所有的setXXX方法,给对象赋值

大家如果需要学习其他Java知识点,戳这里 超详细的Java知识点汇总

你可能感兴趣的:(Java基础,java,jdbc,orm,mysql,数据库)