Spring中的数据拜访,JdbcTemplate应用及源码剖析
前言
本系列文章为事务专栏剖析文章,整个事务剖析专题将按上面这张图实现
image-20200718220712800
对源码剖析前,我心愿先介绍一下Spring中数据拜访的相干内容,而后层层递进到事物的源码剖析,次要分为两个局部
-
JdbcTemplate
应用及源码剖析 -
Mybatis
的根本应用及Spring对Mybatis
的整合
本文将要介绍的是第一点。
JdbcTemplate应用示例
public class DmzService {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
/**
* 查问
* @param id 依据id查问
* @return 对应idd的user对象
*/
public User getUserById(int id) {
return jdbcTemplate
.queryForObject("select * from `user` where id = ?", new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setAge(rs.getInt("age"));
user.setName(rs.getString("name"));
return user;
}
}, id);
}
public int saveUser(User user){
return jdbcTemplate.update("insert into user values(?,?,?)",
new Object[]{user.getId(),user.getName(),user.getAge()});
}
}
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("tx.xml");
DmzService dmzService = cc.getBean(DmzService.class);
User userById = dmzService.getUserById(1);
System.out.println("查问的数据为:" + userById);
userById.setId(userById.getId() + 1);
int i = dmzService.saveUser(userById);
System.out.println("插入了" + i + "条数据");
}
}
数据库中目前只有一条数据:
image-20200708153438245
配置文件如下:
<?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.xsd">
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
<property name="password" value="123"/>
<property name="username" value="root"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/test?serverTimezone=UTC"/>
</bean>
<bean id="dmzService" class="com.dmz.spring.tx.service.DmzService">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
程序容许后果:
查问的数据为:User{id=1, name='dmz', age=18}
插入了1条数据
image-20200708153656393
运行后数据库中的确插入了一条数据
对于JdbcTemplate
的简略应用,倡议大家还是要有肯定相熟,尽管我当初在我的项目中不会间接应用JdbcTemplate
的API。本文对于应用不做过多介绍,次要目标是剖析它底层的源码
JdbcTemplate源码剖析
咱们间接以其queryForObject
办法为入口,对应源码如下:
queryForObject办法剖析
public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args) throws DataAccessException {
// 外围在这个query办法中
List<T> results = query(sql, args, new RowMapperResultSetExtractor<>(rowMapper, 1));
// 这个办法很简略,就是返回后果集中的数据
// 如果少于1条或者多余1条都报错
return DataAccessUtils.nullableSingleResult(results);
}
query办法剖析
// 第一步,对传入的参数进行封装,将参数封装成ArgumentPreparedStatementSetter
public <T> T query(String sql, @Nullable Object[] args, ResultSetExtractor<T> rse) throws DataAccessException {
return query(sql, newArgPreparedStatementSetter(args), rse);
}
// 第二步:对sql语句进行封装,将sql语句封装成SimplePreparedStatementCreator
public <T> T query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor<T> rse) throws DataAccessException {
return query(new SimplePreparedStatementCreator(sql), pss, rse);
}
public <T> T query(
PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, final ResultSetExtractor<T> rse)
throws DataAccessException {
// query办法在实现对参数及sql语句的封装后,间接调用了execute办法
// execute办法是jdbcTemplate的根本API,不论是查问、更新还是保留
// 最终都会进入到这个办法中
return execute(psc, new PreparedStatementCallback<T>() {
@Override
@Nullable
public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
ResultSet rs = null;
try {
if (pss != null) {
pss.setValues(ps);
}
rs = ps.executeQuery();
return rse.extractData(rs);
}
finally {
JdbcUtils.closeResultSet(rs);
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}
});
}
execute办法剖析
// execute办法封装了一次数据库拜访的基本操作
// 例如:获取连贯,开释连贯等
// 其定制化操作是通过传入的PreparedStatementCallback参数来实现的
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)
throws DataAccessException {
// 1.获取数据库连贯
Connection con = DataSourceUtils.getConnection(obtainDataSource());
PreparedStatement ps = null;
try {
// 2.获取一个PreparedStatement,并利用用户设定的参数
ps = psc.createPreparedStatement(con);
applyStatementSettings(ps);
// 3.执行sql并返回后果
T result = action.doInPreparedStatement(ps);
// 4.解决正告
handleWarnings(ps);
return result;
}
catch (SQLException ex) {
// 出现异常的话,须要敞开数据库连贯
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
String sql = getSql(psc);
psc = null;
JdbcUtils.closeStatement(ps);
ps = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("PreparedStatementCallback", sql, ex);
}
finally {
// 敞开资源
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
JdbcUtils.closeStatement(ps);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
1、获取数据库连贯
对应源码如下:
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
// 这里省略了异样解决
// 间接调用了doGetConnection办法
return doGetConnection(dataSource);
}
doGetConnection
办法是最终获取连贯的办法
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
// 如果应用了事务管理器来对事务进行治理(申明式事务跟编程式事务都依赖于事务管理器)
// 那么在开启事务时,Spring会提前绑定一个数据库连贯到以后线程中
// 这里做的就是从以后线程中获取对应的连接池中的连贯
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
// 记录以后这个连贯被应用的次数,每次调用+1
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(fetchConnection(dataSource));
}
return conHolder.getConnection();
}
Connection con = fetchConnection(dataSource);
// 如果开启了一个空事务(例如事务的流传级别设置为SUPPORTS时,就会开启一个空事务)
// 会激活同步,那么在这里须要将连贯绑定到以后线程
if (TransactionSynchronizationManager.isSynchronizationActive()) {
try {
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {
holderToUse.setConnection(con);
}
// 以后连贯被应用的次数+1(能进入到这个办法,阐明这个连贯是刚刚从连接池中获取到)
// 当开释资源时,只有被应用的次数归为0时才放回到连接池中
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
}
catch (RuntimeException ex) {
// 出现异常时开释连贯,如果开启了事务,不会真正调用close办法敞开连贯
// 而是把以后连贯的应用数-1
releaseConnection(con, dataSource);
throw ex;
}
}
return con;
}
2、利用用户设定的参数
protected void applyStatementSettings(Statement stmt) throws SQLException {
int fetchSize = getFetchSize();
if (fetchSize != -1) {
stmt.setFetchSize(fetchSize);
}
int maxRows = getMaxRows();
if (maxRows != -1) {
stmt.setMaxRows(maxRows);
}
DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());
}
从下面代码能够看出,次要设立了两个参数
-
fetchSize
:该参数的设计目标次要是为了缩小网络交互,当拜访ResultSet
的时候,如果它每次只从服务器读取一条数据,则会产生大量的开销,setFetchSize
的含意在于,当调用rs.next
时,它能够间接从内存中获取而不须要网络交互,进步了效率。这个设置可能会被某些JDBC
驱动疏忽,而且设置过大会造成内存回升 -
setMaxRows
,是将此Statement
生成的所有ResultSet
的最大返回行数限定为指定数,作用相似于limit。
3、执行Sql
没啥好说的,底层其实就是调用了jdbc
的一系列API
4、解决正告
也没啥好说的,解决Statement
中的正告信息
protected void handleWarnings(Statement stmt) throws SQLException {
if (isIgnoreWarnings()) {
if (logger.isDebugEnabled()) {
SQLWarning warningToLog = stmt.getWarnings();
while (warningToLog != null) {
logger.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() + "', error code '" +
warningToLog.getErrorCode() + "', message [" + warningToLog.getMessage() + "]");
warningToLog = warningToLog.getNextWarning();
}
}
}
else {
handleWarnings(stmt.getWarnings());
}
}
5、敞开资源
最终会调用到DataSourceUtils
的doReleaseConnection
办法,源码如下:
public static void doReleaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) throws SQLException {
if (con == null) {
return;
}
if (dataSource != null) {
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && connectionEquals(conHolder, con)) {
// 阐明开启了事务,那么不会调用close办法,之后将连贯的占用数减1
conHolder.released();
return;
}
}
// 调用close办法敞开连贯
doCloseConnection(con, dataSource);
}
总结
总的来说,这篇文章波及到的内容都是比较简单的,通过这篇文章是心愿让大家对Spring中的数据拜访有肯定理解,相当于热身吧,前面的文章难度会加大,下篇文章咱们将介绍更高级的数据拜访,myBatis
的应用以及基本原理、事务管理以及它跟Spring的整合原理。
如果本文对你由帮忙的话,记得点个赞吧!也欢送关注我的公众号,微信搜寻:程序员DMZ,或者扫描下方二维码,跟着我一起认认真真学Java,踏踏实实做一个coder。
我叫DMZ,一个在学习路上爬行前行的小菜鸟!
本文应用 mdnice 排版