本文主要对 JDBC连接db / Spring-JDBC连接db / Mybatis独立使用 / Spring整合Mybatis 几种db连接和使用的过程作出总结,并对其中的关键环节进行源码分析。
事务部分暂时还没整理,后面会补全。
部分配置文件还没加上,后面会补全。
说明:文中大部分内容来源于阅读《Spring源码深度解析》时的笔记,同时添加了一些自己的理解。书中使用的Spring 5.x版本,代码有点老,但核心原理一直都差不多。
1 JDBC连接db
一般流程
- 在开发环境加载指定db的驱动程序
- 在java程序中加载驱动程序
- 创建数据连接对象
- 创建Statement对象
- 调用Statement对象相关方法执行sql,解析结果ResultSet
- 关闭db连接
/** JDBC连接db的一般流程(样例代码, 去除了非关键逻辑) **/
// 加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
// 创建数据连接对象
Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPsw);
// 创建Statement对象
Statement stmt = conn.createStatement();
// 调用Statement对象相关方法执行sql
ResultSet rs = stmt.executeQuery(sql);
// 解析结果ResultSet
ResultSetMetaData rsmd = rs.getMetaData();
int columnsNumber = rsmd.getColumnCount();
List<Map> result = new ArrayList<>();
while (rs.next()) {
Map<String, Object> map = new HashMap<>();
for (int i = 1; i <= columnsNumber; i++) {
map.put(rsmd.getColumnName(i), rs.getString(i));
}
result.add(map);
}
// 完成后关闭资源
rs.close();
stmt.close();
conn.close();
2 Spring-JDBC连接db
一般流程
- 创建db表,假设为user
CREATE TABLE 'user' (
'id' int(11) NOT NULL auto_increment,
'name' varchar(64) default '' NOT NULL,
PRIMARY KEY ('id')
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- 创建对应的PO,假设为UserPO
public class UserPO {
private Integer id;
private String name;
// 省略get/set等方法
}
- 创建表与实体间的映射
public class UserRowMapper implements RowMapper {
@Override
public Object mapRow(ResultSet rs, int index) {
UserPO user = new UserPO(rs.getInt("id"), rs.getString("name"));
return user;
}
}
- 创建数据操作接口
public interface UserService {
void save(UserPO user);
List<UserPO> getUsers();
}
- 创建数据操作接口实现类
public class UserServiceImpl implements UserService {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new jdbcTemplate(dataSource);
}
public void save(UserPO user) {
jdbcTemplate.update("insert into user(name) values(?)", new Object[] {
user.getName()}, new int[] {
java.sql.Types.VARCHAR});
}
@SuppressWarnings("unchecked")
public List<UserPO> getUsers() {
List<UserPO> users = jdbcTemplate.query("select * from user", new UserRowMapper());
return users;
}
}
上面代码中引用JdbcTemplate的update()/query()等方法,底层核心方法为execute()。下面讲解下execute()方法(为便于凸显核心逻辑,使用的源码版本比较低),不感兴趣的同学可以直接跳过本部分。
- sql = getSql(preparedStatementCreator)
- Connection con = DataSourceUtils.getConnection(getDataSource)
1 conHolder = TransactionSychronizationManager.getResource(dataSource)
2 如果conHoloder里面有可用connection则复用
3 否则从dataSource获取 connection
4 线程中的数据库事务操作都要使用同一个事务链接 - 引用用户设定的输入参数
applyStatementSettings(preparedStatement), 其中会调用setFetchSize()/setMaxRows()等 - 调用回调 result = action.doInPreParedStatement(psToUse)
1 对update操作
1.1 pss.setValues(ps) 遍历每个参数来做类型匹配及转换。如果是集合类则需要进入集合类内部递归解析集合内部属性。pss.setValues 调用 doSetValue() 对单个参数及类型做匹配处理。pss代表的类是ArgPreparedStatementSetter。
1.2 return ps.executeUpdate() 真正执行sql
1.3 finally 语句块会确保执行 pss.cleanupParameters()
2 对query操作
2.1 resultSet = ps.executeQuery()
2.2 return rse.extractData(rsToUser) 将结果封装并转换成POJO: 对返回结果遍历并对其使用rowMapper进行转换。rse 代表的类为 RowMapperResultSetExtractor.
2.3 finally 语句块会确保执行 pss.cleanupParameters()
3 handleWarnings(preparedStatement) 警告处理。
警告不是异常,它表示很可能出现数据错误,但不一定会影响程序执行,比如DataTruncation,用户可以自己设置处理警告的方式。
4 exception 时会调用 DataSourceUtils.releaseConnection(con, getDataSource)
如果当前线程存在事务,说明存在公用数据库连接,这是直接使用ConnectionHolder中的released方法进行连接数减一,而不是真正的释放连接。
5 finally 语句块会确保执行:
JdbcUtils.closeStatement(preparedStatement); DataSourceUtils.releaseConnection(con, getDataSource())
- 配置spring配置文件
最重要的两个配置: 1 配置dataSource;2 配置业务bean(UserService),并向其中中注入dataSource
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd "