CREATE TABLE `student` (
`number` int(11) NOT NULL AUTO_INCREMENT COMMENT '学号',
`name` varchar(5) DEFAULT NULL COMMENT '姓名',
`major` varchar(30) DEFAULT NULL COMMENT '专业',
PRIMARY KEY (`number`)
) ENGINE=InnoDB AUTO_INCREMENT=20180104 DEFAULT CHARSET=utf8 COMMENT='学生信息表'
public class Student {
private Integer number;
private String name;
private String major;
//省略set和get方法以及构造函数
}
public class StudentMapper implements RowMapper {
@Override
public Object mapRow(ResultSet resultSet, int i) throws SQLException {
return new Student(resultSet.getInt("number"),
resultSet.getString("name"),
resultSet.getString("major"));
}
}
public interface StudentService {
void save(Student student);
List<Student> getStudents();
}
public class StudentServiceImpl implements StudentService {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource){
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public void save(Student student) {
jdbcTemplate.update("insert into student (name,major) values(?,?)",new Object[]{
student.getName(),student.getMajor()
});
}
@Override
public List<Student> getStudents() {
return jdbcTemplate.query("select * from student", new StudentMapper());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd" default-autowire="byName">
<!-- 配置数据源-->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="maxIdle" value="30" />
<property name="defaultAutoCommit" value="true" />
<property name="removeAbandonedTimeout" value="60" />
</bean>
<!-- 配置业务bean-->
<bean id="studentService" class="com.spring.service.StudentServiceImpl">
<!--注入数据源-->
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("config/applicationContext.xml");
StudentService studentService = applicationContext.getBean("studentService", StudentServiceImpl.class);
Student student = new Student("李四","物联网工程");
studentService.save(student);//保存方法
List<Student> students = studentService.getStudents();//查询方法
System.out.println(students);
}
输出结果如下:
[Student{number=20180101, name='杜子腾', major='软件学院'},
Student{number=20180102, name='范统', major='计算机科学与工程'},
Student{number=20180103, name='史珍香', major='计算机科学与工程'},
Student{number=20180104, name='张三', major='计算机科学与技术'},
Student{number=20180105, name='李四', major='物联网工程'}]
基于上述的Spring连接数据库操作中save->update,getStudents->query方法作为分析切入点,关于jdbcTemplate的赋值,示例中使用setDataSource方法初始化值,结合配置文件中bean的定义进行注入,本次分析的重点为update/query方法。
归类:
JdbcTemplate类中执行内部方法update,封装暴露给外部调用的接口主要是为了封装参数,为进一步执行SQL做足准备工作,使用ArgumentPreparedStatementSetter或newArgTypePreparedStatementSetter进行内部封装,然后再一步使用SimplePreparedStatementCreator封装,最后统一委托给内部update,底层执行是由execute执行SQL并将结果返回到updateCount方法并验证结果返回。
update整体流程如下:
//重载外部暴露update方法1封装newArgPreparedStatementSetter
public int update(String sql, @Nullable Object... args) throws DataAccessException {
return update(sql, newArgPreparedStatementSetter(args));
}
//重载外部暴露update方法2封装newArgTypePreparedStatementSetter
public int update(String sql, Object[] args, int[] argTypes) throws DataAccessException {
return update(sql, newArgTypePreparedStatementSetter(args, argTypes));
}
//JdbcTemplate封装SimplePreparedStatementCreator对象,也可暴露
public int update(String sql, @Nullable PreparedStatementSetter pss) throws DataAccessException {
return update(new SimplePreparedStatementCreator(sql), pss);//调用内部update方法
}
//JdbcTemplate内部执行方法,execute执行SQL、updateCount结果验证
protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss)
throws DataAccessException {
/**
* execute处理SQL、查询结果、告警等
* updateCount负责校验结果
*/
return updateCount(execute(psc, ps -> {
try {
if (pss != null) {
pss.setValues(ps);//设置参数值包含参数?替换、value值设置等
}
int rows = ps.executeUpdate();//执行SQL语句
if (logger.isTraceEnabled()) {
logger.trace("SQL update affected " + rows + " rows");
}
return rows;
}
finally {
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}));
}
JdbcTemplate中的update、query等方法都是内部调用底层execute通用方法来执行SQL,可以执行DDL语句、DDM语句和DQL语句,直接使用则通常用于DDL语句,。
execute底层方法核心步骤:
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)
throws DataAccessException {
//验证psc和action回调方法不为空
Assert.notNull(psc, "PreparedStatementCreator must not be null");
Assert.notNull(action, "Callback object must not be null");
//是否开启debugger级别的日志输出
if (logger.isDebugEnabled()) {
String sql = getSql(psc);
logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));
}
//获取数据库连接(1)
Connection con = DataSourceUtils.getConnection(obtainDataSource());
PreparedStatement ps = null;
try {
//创建预编译PreparedStatement类型的SQL对象
ps = psc.createPreparedStatement(con);
//设置查询性能条件如一次查询多少数据、查询超时时间等(2)
applyStatementSettings(ps);
//执行内部update、query中的lamda回调方法(3)
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 {
//执行结束时释放内存中的流(5)
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
JdbcUtils.closeStatement(ps);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
获取连接核心步骤:
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
try {
//spring常规风格,干活的永远是doXXXX
return doGetConnection(dataSource);
}catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex);
}catch (IllegalStateException ex) {
throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + ex.getMessage());
}
}
/**
* 获取连接整体思路
* (1)事务同步器中是否已存在数据库连接;存在则直接返回且连接数共用referenceCount自增;
* (2)fetchConnection方法从数据源中获取新的数据库连接;
* (3)是否开启事务同步支持,事务中要使用同一连接且引用referenceCount自增;
* (4)事务注册到事务同步器中;
* (5)异常时主动连接引用referenceCount自减;
*/
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");//数据源不为空验证
//获取线程是否已拥有数据库连接
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();//连接数+1,采用共用连接数,避免大量的销毁和创建连接
if (!conHolder.hasConnection()) {
conHolder.setConnection(fetchConnection(dataSource));
}
return conHolder.getConnection();//返回连接
}
// 当前线程无数据库连接时开始着手创建连接
Connection con = fetchConnection(dataSource);
// 当前线程是否支持事务同步
if (TransactionSynchronizationManager.isSynchronizationActive()) {
try {
// 在事务中使用同一个数据库连接
ConnectionHolder holderToUse = conHolder;
//conHolder连接为空,则说明TransactionSynchronizationManager.getResource(dataSource)不存在数据库连接
if (holderToUse == null) {
//使用fetchConnection获取的数据库连接作为事务中的同一连接
holderToUse = new ConnectionHolder(con);
}else {
holderToUse.setConnection(con);
}
//同一数据库连接+1
holderToUse.requested();
//将事务注册到synchronizations变量(ThreadLocal>)中
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);//开启事务同步
if (holderToUse != conHolder) {
//绑定事务
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
}catch (RuntimeException ex) {
// 程序异常时需要主动释放
releaseConnection(con, dataSource);
throw ex;
}
}
return con;
}
SQL执行的额外参数:
protected void applyStatementSettings(Statement stmt) throws SQLException {
//获取用户设置的一次向数据库执行时的ResultSet数量,减少迭代ResultSet时多次访问数据库而设计,与分页查询数量无关
int fetchSize = getFetchSize();
if (fetchSize != -1) {
stmt.setFetchSize(fetchSize);
}
// 获取ResultSet数量的最大值
int maxRows = getMaxRows();
if (maxRows != -1) {
stmt.setMaxRows(maxRows);
}
//设置SQL执行的超时时间
DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());
}
底层execute通用方法中的回调方法doInPreparedStatement,主要是依赖上层调用者中设置的lamda表达式,如update、query中的所示:
public void setValues(PreparedStatement ps) throws SQLException {
if (this.args != null) {
//参数遍历
for (int i = 0; i < this.args.length; i++) {
Object arg = this.args[i];
doSetValue(ps, i + 1, arg);//单个参数类型匹配和参数值设置
}
}
}
//参数类型匹配
protected void doSetValue(PreparedStatement ps, int parameterPosition, Object argValue) throws SQLException {
if (argValue instanceof SqlParameterValue) {
SqlParameterValue paramValue = (SqlParameterValue) argValue;
StatementCreatorUtils.setParameterValue(ps, parameterPosition, paramValue, paramValue.getValue());
} else {
StatementCreatorUtils.setParameterValue(ps, parameterPosition, SqlTypeValue.TYPE_UNKNOWN, argValue);
}
}
//参数设置值
public static void setParameterValue(PreparedStatement ps, int paramIndex, SqlParameter param,
@Nullable Object inValue) throws SQLException {
setParameterValueInternal(ps, paramIndex, param.getSqlType(), param.getTypeName(), param.getScale(), inValue);
}
//实际参数值的替换的方法
private static void setParameterValueInternal(PreparedStatement ps, int paramIndex, int sqlType,
@Nullable String typeName, @Nullable Integer scale, @Nullable Object inValue) throws SQLException {
String typeNameToUse = typeName;
int sqlTypeToUse = sqlType;
Object inValueToUse = inValue;
// 对SqlParameterValue类型的进行单独处理,获取SqlType、TypeName和value
if (inValue instanceof SqlParameterValue) {
SqlParameterValue parameterValue = (SqlParameterValue) inValue;
if (logger.isDebugEnabled()) {
logger.debug("Overriding type info with runtime info from SqlParameterValue: column index " + paramIndex +
", SQL type " + parameterValue.getSqlType() + ", type name " + parameterValue.getTypeName());
}
if (parameterValue.getSqlType() != SqlTypeValue.TYPE_UNKNOWN) {
sqlTypeToUse = parameterValue.getSqlType();
}
if (parameterValue.getTypeName() != null) {
typeNameToUse = parameterValue.getTypeName();
}
inValueToUse = parameterValue.getValue();
}
//日志跟踪
if (logger.isTraceEnabled()) {
logger.trace("Setting SQL statement parameter value: column index " + paramIndex +
", parameter value [" + inValueToUse +
"], value class [" + (inValueToUse != null ? inValueToUse.getClass().getName() : "null") +
"], SQL type " + (sqlTypeToUse == SqlTypeValue.TYPE_UNKNOWN ? "unknown" : Integer.toString(sqlTypeToUse)));
}
//设置值
if (inValueToUse == null) {
setNull(ps, paramIndex, sqlTypeToUse, typeNameToUse);
} else {
setValue(ps, paramIndex, sqlTypeToUse, typeNameToUse, scale, inValueToUse);
}
}
SQLWarning主要是用于访问数据库时出现的警告异常,这些告警的信息可以从Connection、Statement和ResultSet对象中获取,如在关闭的连接上或结果集中获取警告将会抛出异常,用户主要是可以设置处理告警的两种方式:一种是默认忽略警告,只打印警告;另一种则是直接抛出异常也就是代码中的else分支代码逻辑。
protected void handleWarnings(Statement stmt) throws SQLException {
//是否忽略告警信息
if (isIgnoreWarnings()) {
//是否开启日志打印
if (logger.isDebugEnabled()) {
SQLWarning warningToLog = stmt.getWarnings();//常见的异常时DataTruncation
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());
}
}
释放资源步骤:
//releaseConnection概念性封装,用于給上层统一调用
public static void releaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) {
try {
doReleaseConnection(con, dataSource);
} catch (SQLException ex) {
logger.debug("Could not close JDBC Connection", ex);
} catch (Throwable ex) {
logger.debug("Unexpected exception on closing JDBC Connection", ex);
}
}
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);
//当前线程存在事务时说明存在共用数据库连接直接使用ConnectionHolder中的released方法进行连接数自减,而不是真正释放连接
if (conHolder != null && connectionEquals(conHolder, con)) {
// 事务中的连接引用自减
conHolder.released();
return;
}
}
doCloseConnection(con, dataSource);
}
//关闭连接
public static void doCloseConnection(Connection con, @Nullable DataSource dataSource) throws SQLException {
if (!(dataSource instanceof SmartDataSource) || ((SmartDataSource) dataSource).shouldClose(con)) {
con.close();
}
}
query方法与update类似,原理大体相同,不再进行详细分析。
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
// 查询器的封装RowMapperResultSetExtractor
return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
}
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
//SQL和rse不为空验证
Assert.notNull(sql, "SQL must not be null");
Assert.notNull(rse, "ResultSetExtractor must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL query [" + sql + "]");
}
/**
* 回调doInStatement方法与update中的回调类似
*/
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
@Override
@Nullable
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
try {
rs = stmt.executeQuery(sql);//执行SQL查询
return rse.extractData(rs);
} finally {
JdbcUtils.closeResultSet(rs);
}
}
@Override
public String getSql() {
return sql;
}
}
// 传入通用底层execute方法进行操作数据库
return execute(new QueryStatementCallback());
}
JdbcTmeplate原理总结: