JDBC

JDBC: Java Database Connectivity ------> 通过Java代码连接\操作关系型数据库

JDBC是Sun公司提供的一套操作所有关系型数据库的接口(标准).各个关系型数据库厂商会按照这个接口提供一套连接自家数据库的实现类,将这些实现类打成Jar包,即数据库驱动Jar包.程序员只需学习JDBC接口,既可以通过这些驱动Jar包连接不同的数据库

核心API

  • java.sql.Connection 数据库连接对象,连接上数据库
  • java.sql.PreparedStatement 根据数据库连接对象conn获得发送SQL对象
  • java.sql.ResultSet 结果集对象,保存查询语句的查询结果
  • java.sql.Driver 数据库驱动对象
  • java.sql.DriverManager 数据库驱动管理对象

核心步骤

create table t_person(
	id int primary key auto_increment,
       name varchar(10) not null,
       age int,
       sex varchar(2),
       phonenum varchar(11) unique,
       address varchar(100)
);

通过数据库客户端操作数据库:

  1. 登录数据库
  2. 打开编辑SQL窗口
  3. 编写SQL
  4. 执行SQL
  5. 查看结果
  6. 关闭数据库

通过JDBC操作数据库:

  1. 加载驱动 -- 将jar包中的代码加载到JVM中
  2. 连接数据库 -- 借助数据库连接对象Connection(数据库连接地址,账号,密码)
  3. 编写SQL
  4. 发送SQL -- PreparedStatement ps ,发送SQL对象
  5. 处理SQL执行结果 ResultSet
  6. 释放资源 ResultSet PreparedStatement Connection 先打开的后关闭 调用各自的.close()即可

第一个JDBC代码

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;

public class JDBCDemo1 {
	public static void main(String[] args) throws Exception {
		//0.导入驱动jar包
		/*
		 * 0_1. 在项目根目录下创建文件夹lib
		 * 0_2. 将jar包mysql-connector-java-8.0.15.jar复制粘贴到lib文件夹中
		 * 0_3.右键单击jar包,选择 Build Path -> Add to Build Path 即可
		 * */
		//1. 加载驱动
		Class.forName("com.mysql.cj.jdbc.Driver");
		//2. 获取数据库连接对象
		//第一个参数: url 代表数据库连接地址 jdbc:mysql://本机IP:端口号/数据库名?设置编码格式&设置时区
		//第二个参数: user 代表数据库账号
		//第三个参数: password 代表数据库密码
		Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3?useEncoding=true&characterSet=utf-8&serverTimezone=GMT%2B8", "root", "root");
		//3. 编写SQL
		//String sql = "insert into t_person values(0,'李四花',18,'女','12345678901','硅谷大厦A座14楼')";
		//String sql = "update t_person set age = 19 where id = 1";
		String sql = "delete from t_person where id = 3";
		//4.发送SQL  ps依赖于conn及SQL语句创建出来的
		PreparedStatement ps = conn.prepareStatement(sql);
		//executeUpdate() 用来发送DML语句,返回值代表影响行数
		//executeQuery() 用来发送DQL语句,返回值为ResultSet对象,其中保存了查询结果
		int n = ps.executeUpdate();
		//5. 处理结果省略
		//6. 释放资源
		ps.close();
		conn.close();
	}
}

ResultSet

class JDBCDemo2 {
	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			//1. 加载驱动
			Class.forName("com.mysql.cj.jdbc.Driver");
			//2.获取数据库连接对象
			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3?useEncoding=true&characterSet=utf-8&serverTimezone=GMT%2B8", "root", "root");
			//3.编写SQL
			String sql = "select * from t_person where id = 1";
			//4.发送SQL
			ps = conn.prepareStatement(sql);
			//5.处理查询结果
			rs = ps.executeQuery();
			if(rs.next()) {
				int id = rs.getInt("id");
				String name = rs.getString("name");
				int age = rs.getInt(3);
				String sex = rs.getString("sex");
				String phonenum = rs.getString("phonenum");
				String address = rs.getString("address");
				System.out.println(id+" "+name+" "+age);
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			if(rs!=null) {
				try {
					rs.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			if(ps!=null) {
				try {
					ps.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			if(conn!=null) {
				try {
					conn.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
}
============================================================================
class JDBCDemo3 {
	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			//1. 加载驱动
			Class.forName("com.mysql.cj.jdbc.Driver");
			//2.获取数据库连接对象
			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3?useEncoding=true&characterSet=utf-8&serverTimezone=GMT%2B8", "root", "root");
			//3.编写SQL
			String sql = "select * from t_person";
			//4.发送SQL
			ps = conn.prepareStatement(sql);
			//5.处理查询结果
			rs = ps.executeQuery();
			while(rs.next()) {
				int id = rs.getInt("id");
				String name = rs.getString("name");
				int age = rs.getInt(3);
				String sex = rs.getString("sex");
				String phonenum = rs.getString("phonenum");
				String address = rs.getString("address");
				System.out.println(id+" "+name+" "+age);
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			if(rs!=null) {
				try {
					rs.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			if(ps!=null) {
				try {
					ps.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			if(conn!=null) {
				try {
					conn.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
}

JDBCUtil

JDBCUtil: JDBC工具类,将相同相似的功能抽取成一个方法,完成功能的复用

特点:

  1. 简化代码,减少代码冗余
  2. 提高代码的复用性

版本1

思路:将获取连接对象及释放资源的代码写成JDBCUtil中的独立的方法

class JDBCUtil1 {
	
	//获取数据库连接对象
	public static Connection getConnection() {
		Connection conn = null;
		try {
			//1. 加载驱动
			Class.forName("com.mysql.cj.jdbc.Driver");
			//2.获取数据库连接对象
			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3?useEncoding=true&characterSet=utf-8&serverTimezone=GMT%2B8", "root", "root");
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//3.返回连接对象
		return conn;
	}
	
	//释放资源 ResultSet  PreparedStatement Connection
	public static void closeAll(ResultSet rs,PreparedStatement ps,Connection conn) {
		if(rs!=null) {
			try {
				rs.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(ps!=null) {
			try {
				ps.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(conn!=null) {
			try {
				conn.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	public static void closeAll(PreparedStatement ps,Connection conn) {
		closeAll(null, ps, conn);
	}
}

版本2

思路: 将一些数据库连接信息写到配置文件中,方便修改

核心API:

  1. 配置文件: *.properties
  2. Properties 读取配置文件的集合类
  3. InputStream 输入流
class JDBCUtil2 {
	
	//获取数据库连接对象
	public static Connection getConnection() {
		Connection conn = null;
		try (//0. 在src目录下写配置文件
				//1. 通过输入流读取配置文件
				//类加载输入流是以src作为根目录
		 InputStream is = JDBCUtil2.class.getClassLoader().getResourceAsStream("jdbc.properties")) {
			
			//2_1. 创建配置文件集合对象
			Properties pp = new Properties();
			//2_2.将配置文件中的数据读取到该集合对象中
			pp.load(is);
			
			//3. 加载驱动 
			Class.forName(pp.getProperty("driverClass"));
			conn = DriverManager.getConnection(pp.getProperty("url"), pp.getProperty("user"), pp.getProperty("password"));
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return conn;
	}
	
	//释放资源 ResultSet  PreparedStatement Connection
	public static void closeAll(ResultSet rs,PreparedStatement ps,Connection conn) {
		if(rs!=null) {
			try {
				rs.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(ps!=null) {
			try {
				ps.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(conn!=null) {
			try {
				conn.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	public static void closeAll(PreparedStatement ps,Connection conn) {
		closeAll(null, ps, conn);
	}
}

版本3

思路: 配置文件的读取不需要频繁进行,只需要读取一次即可

class JDBCUtil3 {
	//private 是为了控制pp的访问权限
	//static 是为了让pp成为全类共有,每次调用JDBCUtil3时,拿到的都是同一个pp
	private static Properties pp = new Properties();
	//静态代码块,是为了保证读取配置文件的操作只会进行一次
	static{
		try (//0. 在src目录下写配置文件
				//1. 通过输入流读取配置文件
				//类加载输入流是以src作为根目录
		 InputStream is = JDBCUtil3.class.getClassLoader().getResourceAsStream("jdbc.properties")) {
		
			//2.将配置文件中的数据读取到该集合对象中
			pp.load(is);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	//获取数据库连接对象
	public static Connection getConnection() {
		Connection conn = null;
		try {
			Class.forName(pp.getProperty("driverClass"));
			conn = DriverManager.getConnection(pp.getProperty("url"), pp.getProperty("user"), pp.getProperty("password"));
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return conn;
	}
	
	//释放资源 ResultSet  PreparedStatement Connection
	public static void closeAll(ResultSet rs,PreparedStatement ps,Connection conn) {
		if(rs!=null) {
			try {
				rs.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(ps!=null) {
			try {
				ps.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(conn!=null) {
			try {
				conn.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	public static void closeAll(PreparedStatement ps,Connection conn) {
		closeAll(null, ps, conn);
	}
}

数据库连接池(DataSource)

概念: 是一个存放数据库连接对象(Connection)的容器

原理: 程序启动时,会创建出数据库连接池,并且池中预先创建好了若干个数据库连接对象;当程序需要使用连接对象操作数据库,可以从池中获取,使用完毕后,再归还给数据库连接池

好处:

  1. 避免了数据库连接对象的频繁创建与销毁,节省系统资源,提高效率
  2. 需要使用连接对象,可以直接获取,不需要等待创建的过程,提高效率

注意: 数据库连接池与JDBC一样,都是Sun公司提供的一套接口,由数据库厂商或其他公司负责实现(jar包);也就意味着,使用该技术,需要导入jar包.

使用:

  1. 获取连接: getConnection();
  2. 归还连接: close(); 若数据库连接对象,是由池中获取的,调用该方法,不会销毁该对象,而是归还给池子

常见的数据库连接池技术: Druid , C3P0 , dbcp等

Druid

Druid : 德鲁伊 ,由阿里巴巴公司提供,目前堪称国内最好用的数据库连接池技术

使用步骤:

  1. 导入jar包 druid-1.0.9.jar

  2. 提供配置文件 druid.properties

    # 加载驱动地址
    driverClassName=com.mysql.cj.jdbc.Driver
    # 数据库连接地址
    url=jdbc:mysql://localhost:3306/db3?useEncoding=true&characterSet=utf-8&serverTimezone=GMT%2B8
    username=root
    password=root
    # 初始连接数量
    initialSize=5
    # 最大连接数量
    maxActive=10
    # 最长等待时间
    maxWait=3000
    
  3. 获取数据库连接池 DruidDataSourceFactory

  4. 获取连接对象 getConnection()

版本4

思路: 使用数据库连接池技术

import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSourceFactory;

public class JDBCUtil4 {
	private static DataSource ds;
	static {
		try {
			//获得读取配置文件的输入流
			InputStream is = JDBCUtil4.class.getClassLoader().getResourceAsStream("druid.properties");
			//创建加载配置文件的集合对象
			Properties pp = new Properties();
			//load()可以将流中的数据读取到集合对象pp中
			pp.load(is);
			//通过DruidDataSourceFactory数据库连接池工厂对象,获取数据库连接池对象
			ds = DruidDataSourceFactory.createDataSource(pp);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	//获取数据库连接对象
	public static Connection getConnection() {
		Connection conn = null;
		try {
			//通过数据库连接池获取数据库连接对象
			conn = ds.getConnection();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return conn;
	}
	
	//释放资源 ResultSet  PreparedStatement Connection
	public static void closeAll(ResultSet rs,PreparedStatement ps,Connection conn) {
		if(rs!=null) {
			try {
				rs.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(ps!=null) {
			try {
				ps.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(conn!=null) {
			try {
				conn.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	public static void closeAll(PreparedStatement ps,Connection conn) {
		closeAll(null, ps, conn);
	}
}

动态参数

需求: 实现登录功能 具有用户表t_user, 含有字段 id ,name ,password

思路:

  1. 通过黑窗口接受用户的用户名及密码
  2. 通过JDBC去数据库中查询使用存在此用户名及密码
  3. 若存在,则返回true,表示登录成功
  4. 若不存在,则返回false,表示登录失败

方式1: 字符串拼接

原理: 通过字符串拼接的方式,将动态参数拼接到字符串SQL中

-- 例如:
String sql = "select * from t_user where name ='"+name+"' and password = '"+pw+"'";

缺点:

  1. 拼接麻烦,操作繁琐
  2. 存在SQL注入的问题; 在拼接时,有可能拼接上一下奇奇怪怪的东西,导致数据不安全(数据丢失,数据修改.....)
 class TestLogin {
	public static void main(String[] args) throws Exception {
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入用户名:");
		String name = sc.next();
		System.out.println("请输入密码:");
		String pw = sc.next();
		//1. 通过JDBCUtil获取conn
		Connection conn = JDBCUtil3.getConnection();
		//2. 编写SQL
		String sql = "select * from t_user where name ='"+name+"' and password = '"+pw+"'";
		System.out.println(sql);
		//3. 获取ps
		PreparedStatement ps = conn.prepareStatement(sql);
		//4.发送\执行SQL
		ResultSet rs = ps.executeQuery();
		if(rs.next()) {
			System.out.println("登录成功");
		}else {
			System.out.println("登录失败");
		}
		JDBCUtil3.closeAll(rs, ps, conn);
	}
}

方式2: 占位符

原理: 在获取PreparedStatement对象时,允许使用?暂时代替一些数据,等到数据确定时,再讲真实数据绑定到相应的?上

-- 例如:
String sql = "select * from t_user where name = ? and password = ?";

注意:

  1. 不允许对关键字,表名,字段名占位

    ? * ? ? ? ? = ? ? = ?;
    
  2. 在调用ps.executeXxx()方法之前,需要为?赋值

    ps.setXxx(int 第几个?,确切的数据);  ?从1开始计数
    例如:
    	ps.setString(1,name);
    	ps.setString(2,pw);
    
class TestLogin2 {
	public static void main(String[] args) throws Exception {
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入用户名:");
		String name = sc.next();
		System.out.println("请输入密码:");
		String pw = sc.next();
		//1. 通过JDBCUtil获取conn
		Connection conn = JDBCUtil3.getConnection();
		//2. 编写SQL
		String sql = "select * from t_user where name = ? and password = ?";
		//3. 获取ps
		PreparedStatement ps = conn.prepareStatement(sql);
		//3_1 绑定参数
		ps.setString(1, name);
		ps.setString(2, pw);
		//4.发送\执行SQL
		ResultSet rs = ps.executeQuery();
		
		if(rs.next()) {
			System.out.println("登录成功");
		}else {
			System.out.println("登录失败");
		}
		JDBCUtil3.closeAll(rs, ps, conn);
	}
}

项目实战设计模式

ORM

ORM: Object Relational Mapping 将表中数据映射成Java中的对象

特点:

  1. 更加贴合Java纯面向对象的编程思想
  2. 方便数据的管理
数据库 Java
表名(t_person) 类名(Person) ,实体类(entity)
一行数据 一个对象
字段名 属性
方法
构造方法
create table t_person(
	id int primary key auto_increment,
    name varchar(10) not null,
    age int,
    sex varchar(2),
    phonenum varchar(11) unique,
    address varchar(100)
);
-- ----------------------------------------
class Person{
	private Integer id;
    private String name;
    private Integer age;
    private String sex;
    private String phonenum;
    private String address;
    //get\set方法
    //构造方法
    //方法
}

DAO

DAO: Data Access Object 数据访问对象,就是专门用来操作数据库,做增删改查等操作

注意:

  1. 一般,dao类中都会具有增\删\改\查一个\查多个这5个方法,但是可以根据具体需求增加新的方法
  2. 为了满足职能单一原则,dao中的每个方法都只做一个操作
class PersonDao{
    
    //增  往表中添加数据 --将p对象的数据添加到表中
    public void insert(Person p){
        
    }
    //删  删除表中某条数据 --根据id删除某条数据
    public void delete(int id){
        
    }
    //改  修改表中某条数据
    public void update(Person p){
        
    }
    //查一个
    public Person selectOne(int id){
        
    }
    //查多个
    public List<Person> selectAll(){
        
    }
}
==========================================================================
企业开发规范写法:
1. 先定义Dao接口
interface PersonDao{
    //增  往表中添加数据 --将p对象的数据添加到表中
    void insert(Person p);
    //删  删除表中某条数据 --根据id删除某条数据
    void delete(int id);
    //改  修改表中某条数据
    void update(Person p);
    //查一个
    Person selectOne(int id);
    //查多个
    List<Person> selectAll();
}
2. 再定义接口实现类
class PersonDaoImpl implements PersonDao{
     //增  往表中添加数据 --将p对象的数据添加到表中
    public void insert(Person p){
        
    }
    //删  删除表中某条数据 --根据id删除某条数据
    public void delete(int id){
        
    }
    //改  修改表中某条数据
    public void update(Person p){
        
    }
    //查一个
    public Person selectOne(int id){
        
    }
    //查多个
    public List<Person> selectAll(){
        
    }
}

补充: 开发思路

  1. 建表
  2. 基于表创建实体类
  3. 定义Dao接口,定义接口中需要的方法
  4. 定义Dao接口实现类DaoImpl ,实现接口中的方法
  5. 测试类测试方法

三层结构

概念: 是一种设计模式,将一个复杂的需求,分为三层: View(视图层),Service(业务层),Dao(dao层)

View(视图层)

作用: 可以和用户做数据交互,负责接收用户数据,并将数据展示给用户

注意: 暂时使用测试类代替, 接收(Scanner),展示(syso)

Service(业务层)

作用: 代表着程序中的具体业务功能,业务层中的每一个方法都对应着程序的一个功能;

​ 例如:登录\注册\搜索\转账等等

代码设计:

  • 业务层接口: XxxService 例如: ProductService
  • 接口实现类: XxxServiceImpl 例如: ProductServiceImpl

注意: 大多数情况下, 每一个业务功能(方法),可能都需要使用多个Dao层的方法;例如转账功能需要使用查\改

所以需要在业务层做事务控制

Connection conn 数据库连接对象

  • 开启事务: conn.setAutoCommit(false);
  • 提交事务: conn.commit();
  • 回滚事务: conn.rollback();

注意: 为了统一管理,无论业务层方法是否需要,都必须进行事务控制

Dao(dao层)

作用: 唯一可以直接操作数据库,dao层中的每个方法都对应在一种数据库操作(增删改查)

代码设计:

  • Dao接口: XxxDao 例如: ProductDao
  • 接口实现类: XxxDaoImpl 例如: ProductDaoImpl

事务控制失败原因:

解决思路: 让Service与Dao使用同一个conn即可

具体实现:

  1. 将Service中的conn当做参数,传递给Service调用的Dao中的每一个方法-----太繁琐\麻烦,弃用!!

  2. Service与Dao都是运行在主线程中,而每一个线程都具有一个独立的空间(线程空间ThreadLocal);在程序执行时,每次都是Service最先获取conn,此时可以新建conn给Service使用,当Service中调用Dao中的方法时,将该conn放入线程空间,Dao不再重新创建新的conn,而是从线程空间中获取,保证了Service与Dao使用的是同一个conn;此时,Dao层中就不能再关闭conn了,因为Service层还要通过该conn控制事务,所以conn的关闭应该由Service负责,Dao层中不再关闭conn!!!

ThreadLocal

概念: 线程空间,每个线程都拥有的一块独立空间,可以用来存储一个变量(数据)

创建语法: ThreadLocal<T> tl = new ThreadLocal<T>();

​ 例如: ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

常用方法:

  • set(T t); 将t放入当前线程空间中
  • T get(); 获取当前线程空间中的数据
  • remove(); 删除线程空间中的数据

应用场景: JDBCUtil

//常规最终版JDBCUtil
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

public class JDBCUtil {
	//private 是为了控制pp的访问权限
	//static 是为了让pp成为全类共有,每次调用JDBCUtil3时,拿到的都是同一个pp
	private static Properties pp = new Properties();
	private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
	//静态代码块,是为了保证读取配置文件的操作只会进行一次
	static{
		try (//0. 在src目录下写配置文件
				//1. 通过输入流读取配置文件
				//类加载输入流是以src作为根目录
		 InputStream is = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties")) {
		
			//2.将配置文件中的数据读取到该集合对象中
			pp.load(is);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	//获取数据库连接对象
	public static Connection getConnection() {
		//从线程空间中获取conn
		Connection conn = tl.get();
		//若conn等于null,说明是Service层第一次来获取conn,此时应该新建conn
		//若conn不等于null,说明是Dao层来获取conn,不再新建,直接return
		if(conn==null) {
			try {
				Class.forName(pp.getProperty("driverClass"));
				conn = DriverManager.getConnection(pp.getProperty("url"), pp.getProperty("user"), pp.getProperty("password"));
				//将Service获取到的conn,放入线程空间中
				tl.set(conn);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return conn;
	}
	
	//释放资源 ResultSet  PreparedStatement Connection
	public static void closeAll(ResultSet rs,PreparedStatement ps,Connection conn) {
		if(rs!=null) {
			try {
				rs.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(ps!=null) {
			try {
				ps.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(conn!=null) {
			try {
				conn.close();
				//将关闭后的conn,从线程空间中移除
				tl.remove();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	public static void closeAll(PreparedStatement ps,Connection conn) {
		closeAll(null, ps, conn);
	}
}
==========================================================================
// 数据库连接池最终版JDBCUtil
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSourceFactory;

public class JDBCUtil5 {
	private static DataSource ds;
	private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
	static {
		try {
			//获得读取配置文件的输入流
			InputStream is = JDBCUtil5.class.getClassLoader().getResourceAsStream("druid.properties");
			//创建加载配置文件的集合对象
			Properties pp = new Properties();
			//load()可以将流中的数据读取到集合对象pp中
			pp.load(is);
			//通过DruidDataSourceFactory数据库连接池工厂对象,获取数据库连接池对象
			ds = DruidDataSourceFactory.createDataSource(pp);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	//获取数据库连接对象
	public static Connection getConnection() {
		Connection conn = tl.get();
		if(conn==null) {
			try {
				//通过数据库连接池获取数据库连接对象
				conn = ds.getConnection();
				tl.set(conn);
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return conn;
	}
	
	//释放资源 ResultSet  PreparedStatement Connection
	public static void closeAll(ResultSet rs,PreparedStatement ps,Connection conn) {
		if(rs!=null) {
			try {
				rs.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(ps!=null) {
			try {
				ps.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(conn!=null) {
			try {
				conn.close();
				tl.remove();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	public static void closeAll(PreparedStatement ps,Connection conn) {
		closeAll(null, ps, conn);
	}
}

批处理

测试:

1W 10W
ps常规操作 1.2 8.46
批处理(BatchUpdate) 0.35 1.06

核心API:

PreparedStatements :

  • addBatch() 将参数保存在Java本地(JVM内存)
  • executeBatch() 将保存在本地的多组参数一次发送给数据库
# 注意,若想使用批处理,数据库连接必须增加参数  rewriteBatchedStatements=true
driverClass=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/db3?useEncoding=true&characterSet=utf-8&serverTimezone=GMT%2B8&rewriteBatchedStatements=true
user=root
password=root
// ps常规操作
class TestPS {
	public static void main(String[] args) throws Exception {
		long t1 = System.nanoTime();
		Connection conn = JDBCUtil.getConnection();
		conn.setAutoCommit(false);
		String sql = "insert into t_user values(0,?,?)";
		PreparedStatement ps = conn.prepareStatement(sql);
		for(int i=1;i<=100000;i++) {
			ps.setString(1,"张三"+i);
			ps.setString(2,i+"");
			ps.executeUpdate();
		}
		conn.commit();
		JDBCUtil.closeAll(ps, conn);
		long t2 = System.nanoTime();
		System.out.println((t2-t1)/1E9);
	} 
}
// 批处理
class TestBatch {
	public static void main(String[] args) throws Exception {
		long t1 = System.nanoTime();
		Connection conn = JDBCUtil.getConnection();
		conn.setAutoCommit(false);
		String sql = "insert into t_user values(0,?,?)";
		PreparedStatement ps = conn.prepareStatement(sql);
		for(int i=1;i<=100000;i++) {
			ps.setString(1,"张三"+i);
			ps.setString(2,i+"");
			//将参数保存在java本地(JVM内存)
			ps.addBatch();
			if(i%500==0) {
				//将保持在java本地多组参数一次发送给数据库
				ps.executeBatch();
			}
		}
		//将剩余的参数发送给数据库
		ps.executeBatch();
		conn.commit();
		JDBCUtil.closeAll(ps, conn);
		long t2 = System.nanoTime();
		System.out.println((t2-t1)/1E9);
	}
}

总结

  1. JDBCUtil不需要会写!!!只需要会用,能大致看懂里面的代码即可
  2. 核心API
    1. Connection conn 数据库连接对象
      • prepareStatement(sql) 获取ps
      • setAutoCommit(false) 代表开启事务
      • commit() 提交事务
      • rollback() 回滚事务
      • close() 关闭连接,释放资源
    2. PreparedStatement ps 发送\执行SQL对象
      • setXxx() 绑定参数
      • executeUpdate() 执行DML
      • executeQuery() 执行DQL
      • addBatch() 将参数保存在Java本地(JVM内存)
      • executeBatch() 将保存在本地的参数一次性发送给数据库
      • close() 关闭\释放资源
    3. ResultSet rs 结果集对象
      • next() 判断rs中是否还存在数据
      • getXxx("字段名") 获取当前行中的字段值
      • close() 关闭\释放资源
    4. ThreadLocal 线程空间
      • void set(T t) 将t放入线程空间
      • T get() 获取线程空间中的数据
      • remove() 删除线程空间中的数据
  3. ORM 将表中数据映射成Java对象
  4. 三层结构
    1. View 视图层 与用户做数据交互-- 接受用户的数据,向用户展示数据
    2. Service 业务层 代表具体的业务功能,例如:登录\注册\搜索\转账等
    3. Dao dao层 唯一可以直接操作数据库的模块-- 增删改查
    4. 视图层中需要调用业务层的方法,业务层功能的实现,需要使用Dao层中的方法
  5. 实际开发流程
    1. 建表
    2. 创建项目
    3. 在项目根目录下创建lib文件夹,与src同级,用来存放项目需要jar包
    4. Build Path: 右键单击jar包--> Build Path --> Add to Build Path
    5. 在src中导入jdbc配置文件
    6. 构建Package结构: 公司域名倒置.模块名
      • com.baizhi.entity 实体类
      • com.baizhi.util 工具类
      • com.baizhi.dao Dao接口
      • com.baizhi.dao.impl Dao接口实现类
      • com.baizhi.service Service接口
      • com.baizhi.service.impl Service接口实现类
      • com.baizhi.test 测试类
  6. 单元测试
    • 使用要求:
      • 方法必须是public void 没有形参
      • 在方法上面一行写上 @Test
      • 导入Junit4jar包
  7. 注意:
    • 项目名\类名\接口名 标识符命名规范
    • Service层中所有方法,必须做事务控制
    • Dao中不能关闭conn
    • Service中必须关闭conn