文章目录
JDBC 基础
1. JDBC 概述
基本概念:
- JDBC:Java Database Connectivity,Java 数据库连接
- JDBC 是 Java 提供的一组独立于任何数据库管理系统的 API
- Java提供接口规范,由各个数据库厂商提供接口实现,厂商提供的实现类封装成 jar 文件,也就是我们俗称的数据库驱动 jar 包
lib 包和 resource 包:
- lib 包放各种第三方 jar 包
- resource 放 配置文件
jar包原理
:比如 java.sql 就是 java 内部定义的一套标准接口,然后我们导入的 jar 包就是第三方的实现类。
JDBC 编写步骤
2. Driver
注意
:导入 mysql-connector 包到 lib 文件夹
2.1 加载并注册驱动
语法:
//加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
/*
底层会调用
DriverManager.registerDriver(com.mysql.jdbc.Driver)
自动注册驱动
*/
2.2 建立数据库连接
连接三要素:
-
URL:用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。
-
数据库用户名
-
数据库密码
URL组成
**JDBC URL的标准由三部分组成,各部分间用冒号分隔。 **
- 协议:JDBC URL中的协议总是jdbc
- 子协议:子协议用于标识一个数据库驱动程序
- 子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库提供足够的信息。包含主机名(对应服务端的ip地址),端口号,数据库名
语法
//第一种: 硬编码
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "Ting123321";
Connection connection = DriverManager.getConnection(url, user, password);
//第二种: 软编码写入 properties
Properties pro = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
pro.load(is);
String url = pro.getProperty("url");
String user = pro.getProperty("user");
String password = pro.getProperty("password");
Connection connection = DriverManager.getConnection(url, user, password);
/*
jdbc.properties 文件
user=root
password=abc123
url=jdbc:mysql://localhost:3306/test
driverClass=com.mysql.jdbc.Driver
*/
3. Connection
作用
- 获取执行 SQL 的对象
- 管理事务
3.1 获执行 SQL 的对象
语法
//获取 普通执行SQL对象: Statement
Statement statement = connection.createStatement();
//获取 预编译SQL执行对象: preparedStatement(sql)
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//获取执行存储过程的对象: CallableStatement(sql)
CallableStatement callableStatement = connection.prepareCall(sql);
3.2 事务管理
语法:
//开启事务: true 自动提交, false 手动提交(就是开启)
connection.setAutoCommit(true);
//提交事务
connection.commit();
//回滚事务
connection.rallback();
导致事务自动提交的操作:
- DDL操作一旦执行,都会自提交
- DML默认情况下,一旦提交,就会自动提交
- 我们可以通过 set autocommit = false 的方式取消 DML 操作的自动提交
- 默认在关闭连接时,会自动提交数据
事务操作步骤:
- 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
- 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
- 在出现异常时,调用 rollback(); 方法回滚事务
若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态 setAutoCommit(true)。尤其是在使用数据库连接池技术时,执行close()方法前,建议恢复自动提交状态。
public void testJDBCTransaction() {
Connection conn = null;
try {
// 1.获取数据库连接
conn = JDBCUtils.getConnection();
// 2.开启事务
conn.setAutoCommit(false);
// 3.进行数据库操作
String sql1 = "update user_table set balance = balance - 100 where user = ?";
update(conn, sql1, "AA");
// 模拟网络异常
//System.out.println(10 / 0);
String sql2 = "update user_table set balance = balance + 100 where user = ?";
update(conn, sql2, "BB");
// 4.若没有异常,则提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
// 5.若有异常,则回滚事务
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
try {
//6.恢复每次DML操作的自动提交功能
conn.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
//7.关闭连接
JDBCUtils.closeResource(conn, null, null);
}
}
4. Statement 与 PreparedStatement
作用:
- 执行 SQL 语句
4.1 执行 SQL 语法
//执行 sql 语句,
//如果是查询语句:true 有数据, false 没数据
//如果是修改语句: true 受影响条数大于0, false 受影响条数小于0
preparedStatement.execute();
//用于执行 DQL
//返回 ResultSet 结果集对象
preparedStatement.executeQuery();
//用于执行 DML,DDL
//返回受影响条数
preparedStatement.executeUpdate();
4.2 Statement
//得到 SQL 语句
Statement statement = con.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT * From t_emp");
int result = statement.executeUpdate("UPDATE t_emp set emp_name = 'angel' where emp_name =
Statement 存在 SQL 注入问题
String name = "张三";
String sql = "SELECT * FROM users WHERE username = '" + name + "'";
//变成 String sql = "SELECT * FROM users WHERE username = 'abc' OR '1' = '1';
//如果用户输入的用户名是 abc' OR '1'='1 由于 abc' OR '1'='1
//这个条件总是为真,所以即使输入的密码不正确,也能够绕过验证登录系统,
4.3 PreparedStatement
//sql语句中的参数值用 ? 占位符替代
String sql = "select * from user where username = ? and password = ? "
//通过Connection 对象获取,并传入对应 SQL
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//设置参数值
//参数值下标从 1 开始
preparedStatement.setString(1, "angel");
preparedStatement.setString(2, "123");
注意
- setObject:方法能够处理各种不同的数据类型。例如,您传递了字符串
"莫扎特"
和整数18
,实际上在内部,setObject
会根据实际传入的对象类型进行适当的转换和处理。**- PreparedStatement是预编译SQL语句,同一SQL语句多次执行的情况下,可以复用,不必每次重新编译和解析。
setObject 能自动匹配,getObject 不行
5. ResultSet 与 ResultSetMetaData
作用:
- 封装 DQL 查询语句的结果
5.1 ResultSet
语法:
//得到 SQL 语句
/*
boolean next(): 最开始指向数据行的前一行,判断结果集的下一条是否有数据,如果有数据返回 true, 并且指针下移,返回 false 指针不会下移
xxx getXXx(参数): 获取数据
参数:
int 列的编号,从1开始
String 列的名称
*/
Statement statement = con.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT * From t_emp");
while (resultSet.next()) {
int emp_id = resultSet.getInt("emp_id");
String emp_name = resultSet.getString("emp_name");
double emp_salary = resultSet.getDouble("emp_salsary");
int emp_age = resultSet.getInt("emp_age");
System.out.println(emp_id + "\t" + emp_name + "\t" + emp_salary + "\t" + emp_age);
}
5.2 ResultSetMetaData
作用:
-
可用于获取关于 ResultSet 对象中列的类型和属性信息的对象
语法
- ResultSetMetaData meta = resultset.getMetaData();
-
getColumnName(int column):获取指定列的名称
-
getColumnLabel(int column):获取指定列的别名,如果没有别名就获取原名
-
getColumnCount():返回当前 ResultSet 对象中的列数。
-
getColumnTypeName(int column):检索指定列的数据库特定的类型名称。
-
getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。
-
isNullable(int column):指示指定列中的值是否可以为 null。
-
isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。
-
常用
如何获取 ResultSetMetaData: 调用 ResultSet 的 getMetaData() 方法即可
获取 ResultSet 中有多少列:调用 ResultSetMetaData 的 getColumnCount() 方法
获取 ResultSet 每一列的列的名字是什么:调用 ResultSetMetaData 的getColumnLabel() 方法
6. 批量操作
当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率
语法:
PreparedStatement ps = conn.prepareStatement(sql);
//积攒需要批量处理的SQL语句
ps.addBatch()
//执行批量处理语句
ps.executeBatch()
//清空缓存的数据
ps.clearBatch()
举例
public class addBatchTest {
@Test
public void test1() throws Exception {
long start = System.currentTimeMillis();
//建立连接
Connection connection = Utils.jdbcConnection();
//执行 SQL
String sql = "insert into goods(name)values(?)";
PreparedStatement ps = connection.prepareStatement(sql);
for(int i = 1;i <= 1000000;i++){
ps.setString(1, "name_" + i);
//1.“攒”sql
ps.addBatch();
if(i % 500 == 0){
//2.执行
ps.executeBatch();
//3.清空
ps.clearBatch();
}
}
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));
//关闭资源
Utils.closeConnection(null, ps, connection);
}
}
7. 常见异常
7.1 SQL语句问题
- SQL语句有错误,检查SQL语句!建议SQL语句在SQL工具中测试后再复制到Java程序中!
- 连接数据库的URL中,数据库名称编写错误,也会报该异常!
7.2 SQL语句未设置参数问题
java.sql.SQLException:No value specified for parameter 1
在使用预编译SQL语句时,如果有?占位符,要为每一个占位符赋值,否则报该错误!
7.3 用户名或密码错误问题
连接数据库时,如果用户名或密码输入错误,也会报SQLException,容易混淆!所以一定要看清楚异常后面的原因描述
7.4 通信异常
在连接数据库的URL中,如果IP或端口写错了,会报如下异常:
com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
8. JDBC 操作总结
1. 注册驱动【依赖的驱动类,进行安装】
2. 获取连接【Connection 建立连接】
3. 创建发送 SQL 语句对象【Connection创建发送SQL语句的Statement】
4. 发送SQL语句,并获取返回结果【Statement 发送 SQL 语句到数据库并取得返回结果】
5. 结果集解析【ResultSet结果集解析,将查询结果解析出来】
6. 资源关闭【释放 ResultSet, Statement, Connection】
public class Test {
public static void main(String[] args) throws Exception{
//1. 注册驱动【依赖的驱动类,进行安装】
DriverManager.registerDriver(new Driver());
//2. 获取连接【Connection 建立连接】
String url = "jdbc:mysql://127.0.0.1:3306/atguigu";
String user = "root";
String password = "Ting123321";
Connection con = DriverManager.getConnection(url, user, password);
//3.创建发送 SQL 语句对象【Connection创建发送SQL语句的Statement】
String sql = "SELECT * FROM t_emp WHERE emp_name = ?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setString(1, "angel");
//4. 发送SQL语句,并获取返回结果【Statement 发送 SQL 语句到数据库并取得返回结果】
ResultSet resultSet = preparedStatement.executeQuery();
//5. 结果集解析【ResultSet结果集解析,将查询结果解析出来】
while (resultSet.next()) {
int empId = resultSet.getInt("emp_id");
String empName = resultSet.getString("emp_name");
double empSalary = resultSet.getDouble("emp_salary");
int empAge = resultSet.getInt("emp_age");
System.out.println(empId + "\t" + empName + "\t" + empSalary + "\t" + empAge);
}
//6.资源关闭【释放 ResultSet, Statement, Connection】
resultSet.close();
preparedStatement.close();
con.close();
}
}
JDBC 进阶
1. ORM 思想
ORM 编程思想 (object relational mapping)
- 一个数据表对应一个 java 类
- 表中一条数据对应 java 类的一个对象
- 表中的一个字段对应 java 类的一个属性
Bean 类
public class orderorm {
private Integer id;
private String orderName;
private Date orderDate;
//省略get、set、无参、有参、toString方法。
}
实操
public class orderOrmTest {
@Test
public void test1 () throws Exception{
//加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//建立连接
Properties properties = new Properties();
properties.load(new FileReader("resource/jdbc.properties"));
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
Connection connection = DriverManager.getConnection(url, user, password);
//预编译 SQL 语句
//1.order 表和 sql 关键字冲突所以要 ``
//2.因为 orm思想实现类里面名字是 orderId 所以要起别名
String sql = "select order_id as orderId, order_name as orderName, order_date as orderDate from `order` where order_id = ?;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1, 2);
//处理返回的 SQL 结果
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
Integer orderId = resultSet.getInt(1);
String orderName = resultSet.getString(2);
Date orderDate = resultSet.getDate(3);
//创建实体类
orderorm ord = new orderorm(orderId, orderName, orderDate);
System.out.println(ord);
}
//关闭资源
resultSet.close();
preparedStatement.clearBatch();
connection.close();
}
}
2. 数据库连接池
连接池就是数据库连接对象的缓冲区,通过配置,由连接池负责创建连接、管理连接、释放连接等操作。
预先创建数据库连接放入连接池,用户在请求时,通过池直接获取连接,使用完毕后,将连接放回池中,避免了频繁的创建和销毁,同时解决了创建的效率。
当池中无连接可用,且未达到上限时,连接池会新建连接。
池中连接达到上限,用户请求会等待,可以设置超时时间。
2.1 常见连接池
JDBC 的数据库连接池使用 javax.sql.DataSource接口进行规范,所有的第三方连接池都实现此接口,自行添加具体实现!也就是说,所有连接池获取连接的和回收连接方法都一样,不同的只有性能和扩展功能!
- DBCP 是Apache提供的数据库连接池,速度相对C3P0较快,但自身存在一些BUG。
- C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。
- Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能, 稳定性较c3p0差一点
- Druid 是阿里提供的数据库连接池,是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,性能、扩展性、易用性都更好,功能丰富。
- Hikari(ひかり[shi ga li]) 取自日语,是光的意思,是SpringBoot2.x之后内置的一款连接池,基于 BoneCP (已经放弃维护,推荐该连接池)做了不少的改进和优化,口号是快速、简单、可靠。
2.2 Druid 连接池使用
2.2.1 Druid 硬编码实现
//1.创建DruidDataSource连接池对象。
DruidDataSource druidDataSource = new DruidDataSource();
//2.设置连接池配置信息
//设置驱动
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
//设置数据库地址
druidDataSource.setUrl("jdbc:mysql:///atguigu");
//数据库名称
druidDataSource.setUsername("root");
//数据库密码
druidDataSource.setPassword("Ting123321");
//3. 设置连接池参参数【非必须】
//设置最大连接池数量 默认是 8
druidDataSource.setMaxActive(int num);
//设置初始化时创建连接的数量
//如果将 initialSize 设置为 5 ,那么在连接池初始化时,会立即创建 5 个可用的连接。
druidDataSource.setInitialSize(int num);
//4.通过连接池获取连接对象
druidDataSource.getConnection();
public class DuridTest {
@Test
public void testHardCodeDruid() throws Exception {
/*
硬编码:将连接池的配置信息和 Java 代码耦合在一起
1.创建DruidDateSoure连接池对象
2.设置连接池的配置信息【一个必须参数|一个非必要参数】
3.通过连接池获取连接对象
4.回收连接【不是释放谅解,而是将连接归还连接池,给其他线程进行复用】
*/
//1.创建DruidDataSource连接池对象。
DruidDataSource druidDataSource = new DruidDataSource();
//2.设置连接池的配置信息【必须 | 非必须】
//2.1 必须设置的配置
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql:///atguigu");
druidDataSource.setUsername("root");
druidDataSource.setPassword("Ting123321");
//2.2 非必须设置的配置
druidDataSource.setInitialSize(10);
druidDataSource.setMaxActive(20);
//3.通过连接池获取连接对象
Connection connection = druidDataSource.getConnection();
System.out.println(connection);
//基于connection进行CRUD
//4.回收连接
connection.close();
}
}
2.2.2 Druid 软编码实现
创建一个文件夹标记为 Resources 放配置文件的文件夹,然后把配置写进 XXX.properties 文件中放进去。我们就可以直接导入配置文件
Druid 软编码实现
创建一个文件夹标记为 Resources 放配置文件的文件夹,然后把配置写进 XXX.properties 文件中放进去。我们就可以直接导入配置文件
连接信息
//创造连接池
DruidDataSourceFactory.createDataSource(properties文件);
# druid连接池需要的配置参数,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///atguigu
username=root
password=atguigu
initialSize=10
maxActive=20
public void testResourceDruid() throws Exception {
//1.创建Properties集合, 用于配置外部配文件的key和value值
Properties properties = new Properties();
//2.读取外部配置文件,获取输入流,加载到Properties集合里
//从根目录开始找 db.properties 文件,根目录就是最外层目录
InputStream inputStream = DuridTest.class.getClassLoader().getResourceAsStream("db.properties");
properties.load(inputStream);
//3.基于Properties集合构建DruidDataSoure连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//4.通过连接池获取连接对象
Connection connection = dataSource.getConnection();
//5.开发 CRUD
//6.回收连接
connection.close();
}
其他配置
配置 | 缺省 | 说明 |
---|---|---|
name | 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this) | |
jdbcUrl | 连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto | |
username | 连接数据库的用户名 | |
password | 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/使用ConfigFilter | |
driverClassName | 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下) | |
initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 |
maxActive | 8 | 最大连接池数量 |
maxIdle | 8 | 已经不再使用,配置了也没效果 |
minIdle | 最小连接池数量 | |
maxWait | 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 | |
poolPreparedStatements | false | 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 |
maxOpenPreparedStatements | -1 | 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 |
validationQuery | 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 | |
testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 |
testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 |
timeBetweenEvictionRunsMillis | 有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明 | |
numTestsPerEvictionRun | 不再使用,一个DruidDataSource只支持一个EvictionRun | |
minEvictableIdleTimeMillis | ||
connectionInitSqls | 物理连接初始化的时候执行的sql | |
exceptionSorter | 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接 | |
filters | 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall | |
proxyFilters | 类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系 |
2.3 Hikari 连接池
2.3.1 硬编码实现
@Test
public void testHardCodeHikari() throws Exception {
//1. 创建 HikariDataSource 连接池对象
//2. 设置连接池配置信息【一个必须|一个非必须】
//3. 通过连接池获取连接对象
//4. 回收连接
//1.创建 HikariDataSource 连接池对象
HikariDataSource hikariDataSource = new HikariDataSource();
//2.设置连接池配置信息
//2.1必须设置
hikariDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
hikariDataSource.setJdbcUrl("jdbc:mysql:///atguigu");
hikariDataSource.setUsername("root");
hikariDataSource.setPassword("Ting123321");
//2.2 非必须设置
hikariDataSource.setMinimumIdle(10);
hikariDataSource.setMaximumPoolSize(20);
//3.通过连接池获取连接对象
Connection connection = hikariDataSource.getConnection();
System.out.println(connection);
//4.回收连接
connection.close();
}
2.3.2 软编码实现
//创建连接池
HikariConfig hikariConfig = new HikariConfig(properties文件);
//properties 文件
driverClassName=com.mysql.cj.jdbc.Driver
jdbcUrl=jdbc:mysql:///atguigu
username=root
password=Ting123321
minimumIdle=10
maximumPoolSize=20
@Test
public void testResourcesHikari() throws Exception {
//1. 创建配置 Properties 集合,用于配置外部配置文
Properties properties = new Properties();
//2.读取配置文件
InputStream inputStream = HikariTest.class.getClassLoader().getResourceAsStream("hikari.properties");
properties.load(inputStream);
//3.创建 HikariConfig连接配置对象,将 Properties 对象传进去
HikariConfig hikariConfig = new HikariConfig(properties);
//4.基于 hikariConfig连接池配置对象,构建HikariDataSource
HikariDataSource hikariDataSource = new HikariDataSource(hikariConfig);
//5.获取连接
Connection connection = hikariDataSource.getConnection();
System.out.println(connection);
//6.回收连接
connection.close();
}
HikariCP其他配置
属性 | 默认值 | 说明 |
---|---|---|
isAutoCommit | true | 自动提交从池中返回的连接 |
connectionTimeout | 30000 | 等待来自池的连接的最大毫秒数 |
maxLifetime | 1800000 | 池中连接最长生命周期如果不等于0且小于30秒则会被重置回30分钟 |
minimumIdle | 10 | 池中维护的最小空闲连接数 minIdle<0或者minIdle>maxPoolSize,则被重置为maxPoolSize |
maximumPoolSize | 10 | 池中最大连接数,包括闲置和使用中的连接 |
metricRegistry | null | 连接池的用户定义名称,主要出现在日志记录和JMX管理控制台中以识别池和池配置 |
healthCheckRegistry | null | 报告当前健康信息 |
poolName | HikariPool-1 | 连接池的用户定义名称,主要出现在日志记录和JMX管理控制台中以识别池和池配置 |
idleTimeout | 是允许连接在连接池中空闲的最长时间 |
JDBC 高级
1. ThreadLocal
JDK 1.2的版本中就提供java.lang.ThreadLocal,为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。通常用来在在多线程中管理共享数据库连接、Session等。
ThreadLocal用于保存某个线程共享变量,原因是在Java中,每一个线程对象中都有一个ThreadLocalMap<ThreadLocal, Object>,其key就是一个ThreadLocal,而Object即为该线程的共享变量。
而这个map是通过ThreadLocal的set和get方法操作的。对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。
ThreadLocal<Connection> threadLocal = null;
//得到共享变量
threadLocal.get();
//设置共享变量
threadLocal.set(T t);
//移除共享变量
threadLocal.remove();
2. Dao
DAO:Data Access Object,数据访问对象。
Java是面向对象语言,数据在Java中通常以对象的形式存在。一张表对应一个实体类,一张表的操作对应一个DAO对象!
在Java操作数据库时,我们会将对同一张表的增删改查操作统一维护起来,维护的这个类就是DAO层。
DAO层只关注对数据库的操作,供业务层Service调用,将职责划分清楚!
BaseDao
基本上每一个数据表都应该有一个对应的DAO接口及其实现类,发现对所有表的操作(增、删、改、查)代码重复度很高,所以可以抽取公共代码,给这些DAO的实现类可以抽取一个公共的父类,复用增删改查的基本操作,我们称为BaseDAO。
各种Dao都是基于 BaseDao
BaseDao -> XXXDao -> XXXDaoimpl
2.1 Utils 工具类
package com.atguigu.senior.util;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/**
* JDBC工具类:
* 1、维护一个连接池对象、维护了一个线程绑定变量的ThreadLocal对象
* 2、对外提供在ThreadLocal中获取连接的方法
* 3、对外提供回收连接的方法,回收过程中,将要回收的连接从ThreadLocal中移除!
* 注意:工具类仅对外提供共性的功能代码,所以方法均为静态方法!
* 注意:使用ThreadLocal就是为了一个线程在多次数据库操作过程中,使用的是同一个连接!
*/
public class JDBCUtilV2 {
//创建连接池引用,因为要提供给当前项目的全局使用,所以创建为静态的。
private static DataSource dataSource;
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
//在项目启动时,即创建连接池对象,赋值给dataSource
static {
try {
Properties properties = new Properties();
InputStream inputStream = JDBCUtil.class.getClassLoader().getResourceAsStream("db.properties");
properties.load(inputStream);
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//对外提供在连接池中获取连接的方法
public static Connection getConnection(){
try {
//在ThreadLocal中获取Connection、
Connection connection = threadLocal.get();
//threadLocal里没有存储Connection,也就是第一次获取
if (connection == null) {
//在连接池中获取一个连接,存储在threadLocal里。
connection = dataSource.getConnection();
threadLocal.set(connection);
}
return connection;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//对外提供回收连接的方法
public static void release(){
try {
Connection connection = threadLocal.get();
if(connection!=null){
//从threadLocal中移除当前已经存储的Connection对象
threadLocal.remove();
//如果开启了事务的手动提交,操作完毕后,归还给连接池之前,要将事务的自动提交改为true
connection.setAutoCommit(true);
//将Connection对象归还给连接池
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
2.2 BaseDao
public abstract class BaseDAO {
/*
通用的增、删、改的方法
String sql:sql
Object... args:给sql中的?设置的值列表,可以是0~n
*/
protected int update(String sql,Object... args) throws SQLException {
// 创建PreparedStatement对象,对sql预编译
Connection connection = JDBCTools.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
//设置?占位符的值
if(args != null && args.length>0){
for(int i=0; i<args.length; i++) {
ps.setObject(i+1,args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
}
}
//执行sql
int len = ps.executeUpdate();
ps.close();
//这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
//connection.getAutoCommit()为false,不要在这里回收connection,由开启事务的地方回收
//connection.getAutoCommit()为true,正常回收连接
//没有开启事务的话,直接回收关闭即可!
if (connection.getAutoCommit()) {
//回收
JDBCTools.release();
}
return len;
}
/*
通用的查询多个Javabean对象的方法,例如:多个员工对象,多个部门对象等
这里的clazz接收的是T类型的Class对象,
如果查询员工信息,clazz代表Employee.class,
如果查询部门信息,clazz代表Department.class,
返回List<T> list
*/
protected <T> ArrayList<T> executeQuery(Class<T> clazz,String sql,Object... args) throws Exception {
// 创建PreparedStatement对象,对sql预编译
Connection connection = JDBCTools.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
//设置?的值
if(args != null && args.length>0){
for(int i=0; i<args.length; i++) {
ps.setObject(i+1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
}
}
ArrayList<T> list = new ArrayList<>();
ResultSet res = ps.executeQuery();
/*
获取结果集的元数据对象。
元数据对象中有该结果集一共有几列、列名称是什么等信息
*/
ResultSetMetaData metaData = res.getMetaData();
int columnCount = metaData.getColumnCount();//获取结果集列数
//遍历结果集ResultSet,把查询结果中的一条一条记录,变成一个一个T 对象,放到list中。
while(res.next()){
//循环一次代表有一行,代表有一个T对象
T t = clazz.newInstance();//要求这个类型必须有公共的无参构造
//把这条记录的每一个单元格的值取出来,设置到t对象对应的属性中。
for(int i=1; i<=columnCount; i++){
//for循环一次,代表取某一行的1个单元格的值
Object value = res.getObject(i);
//这个值应该是t对象的某个属性值
//获取该属性对应的Field对象
//String columnName = metaData.getColumnName(i);//获取第i列的字段名
//这里再取别名可能没办法对应上
String columnName = metaData.getColumnLabel(i);//获取第i列的字段名或字段的别名
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);//这么做可以操作private的属性
field.set(t,value);
}
list.add(t);
}
res.close();
ps.close();
//这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
//没有开启事务的话,直接回收关闭即可!
if (connection.getAutoCommit()) {
//回收
JDBCTools.release();
}
return list;
}
protected <T> T queryBean(Class<T> clazz,String sql, Object... args) throws Exception {
ArrayList<T> list = query(clazz, sql,args);
if(list == null || list.size() == 0){
return null;
}
return list.get(0);
}
}
2.3 EmployeeDao
package com.atguigu.senior.dao;
import com.atguigu.senior.pojo.Employee;
import java.util.List;
/**
* EmployeeDao这个类对应的是t_emp这张表的增删改查的操作
*/
public interface EmployeeDao {
/**
* 数据库对应的查询所有的操作
* @return 表中所有的数据
*/
List<Employee> selectAll();
/**
* 数据库对应的根据empId查询单个员工数据操作
* @param empId 主键列
* @return 一个员工对象(一行数据)
*/
Employee selectByEmpId(Integer empId);
/**
* 数据库对应的新增一条员工数据
* @param employee ORM思想中的一个员工对象
* @return 受影响的行数
*/
int insert(Employee employee);
/**
* 数据库对应的修改一条员工数据
* @param employee ORM思想中的一个员工对象
* @return 受影响的行数
*/
int update(Employee employee);
/**
* 数据库对应的根据empId删除一条员工数据
* @param empId 主键列
* @return 受影响的行数
*/
int delete(Integer empId);
}
2.4 EmployeeDaoimpl
package com.atguigu.senior.dao.impl;
import com.atguigu.senior.dao.BaseDAO;
import com.atguigu.senior.dao.EmployeeDao;
import com.atguigu.senior.pojo.Employee;
import java.util.List;
public class EmployeeDaoImpl extends BaseDAO implements EmployeeDao {
@Override
public List<Employee> selectAll() {
try {
String sql = "SELECT emp_id empId,emp_name empName,emp_salary empSalary,emp_age empAge FROM t_emp";
return executeQuery(Employee.class,sql,null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Employee selectByEmpId(Integer empId) {
try {
String sql = "SELECT emp_id empId,emp_name empName,emp_salary empSalary,emp_age empAge FROM t_emp where emp_id = ?";
return executeQueryBean(Employee.class,sql,empId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int insert(Employee employee) {
try {
String sql = "INSERT INTO t_emp(emp_name,emp_salary,emp_age) VALUES (?,?,?)";
return executeUpdate(sql,employee.getEmpName(),employee.getEmpSalary(),employee.getEmpAge());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int update(Employee employee) {
try {
String sql = "UPDATE t_emp SET emp_salary = ? WHERE emp_id = ?";
return executeUpdate(sql,employee.getEmpSalary(),employee.getEmpId());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int delete(Integer empId) {
try {
String sql = "delete from t_emp where emp_id = ?";
return executeUpdate(sql,empId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
3. Apache-Dbutils
QueryRunner queryRunner = new QueryRunner();
String sql = ...
//orm 思想的 Actor 表
Actor actor = queryRunner.query(Connection connection, String sql, ResultSetHander resultsetHander(类的反射), Object...args);
底层除了 Connection 要自己关,其他它会帮你关
3.1 查
例:返回List集合
QueryRunner queryRunner = new QueryRunner();
String sql = "select order_id as orderid, order_name as orderName, order_date as orderDate from `order` where order_id >= ?;";
List<orderorm> query = queryRunner.query(connection, sql, new BeanListHandler<>(orderorm.class),1);
for (orderorm orm : query) {
System.out.println(orm);
}
connection.close();
}
例: 返回单行单列
//2. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
String sql = "select order_id as orderid, order_name as orderName, order_date as orderDate from `order` where order_id = ?;";
//order 表
orderorm order = queryRunner.query(connection, sql, new BeanHandler<>(order.class), 1);
System.out.println(order);
connection.close();
}
3.2 改(update, insert, delete)
语法
//三个全部封装在 update 里面
//返回受影响条数
queryRunner.update(Connection connection, String sql, Objecj...args);