黑马程序员技术交流社区
标题: 【济南中心】jdbctemplate源码解析 [打印本页]
作者: 大山哥哥 时间: 2018-12-26 21:13
标题: 【济南中心】jdbctemplate源码解析
关于JdbcTemplate
JdbcTemplate是Spring对数据库的操作在jdbc上面做了深层次的封装.
什么是JDBC
JDBC 规范定义接口,具体的实现由各大数据库厂商来实现。JDBC 是 Java 访问数据库的标准规范,真正怎么操作数据库还需要具体的实现类,也就是数据库驱动。每个数据库厂商根据自家数据库的通信格式编写好自己数据库的驱动。所以我们只需要会调用 JDBC 接口中的方法即可,数据库驱动由数据库厂商提供
源码解析
接下来则来对JdbcTemplate的update方法进行一个源码解析吧~
[Java] 纯文本查看 复制代码
JdbcTemplate template = new JdbcTemplate(dataSource);
String sql = "update t_user set name = ? where id = ?";
int count = template.update(sql, "李四",3);
第一步是创建JdbcTemplate对象,传入数据源,进入构造方法查看JdbcTemplate.java
[Java] 纯文本查看 复制代码
第一步是创建JdbcTemplate对象,传入数据源,进入构造方法查看
JdbcTemplate.java
JdbcAccessor.java
[Java] 纯文本查看 复制代码
private DataSource dataSource;
private boolean lazyInit = true;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public DataSource getDataSource() {
return this.dataSource;
}
public boolean isLazyInit() {
return this.lazyInit;
}
public void afterPropertiesSet() {
if (getDataSource() == null) {
throw new IllegalArgumentException("Property 'dataSource' is required");
}
if (!isLazyInit()) {
getExceptionTranslator();
}
}
通过上面代码来看说白了就是用来初始化我们JdbcTemplate对象的,当然也做了不少操作,比如要判断是够有提供dataSource对象
第二步是调用对应的update方法,把sql和参数传入最后接收响应结果创建完我们的jdbcTemplate对象后那么接下来才真正的进入到我们执行增删改的update方法中~当然我们以前面demo进行入手
JdbcTemplate.java
[Java] 纯文本查看 复制代码
public int update(String sql, @Nullable Object... args) throws DataAccessException {
//---------
//标注一:newArgPreparedStatementSetter方法做了什么事?
return update(sql, newArgPreparedStatementSetter(args));
}
public int update(String sql, @Nullable PreparedStatementSetter pss) throws DataAccessException {
//---------
//标注二:在这个地方都做了些什么操作?
return update(new SimplePreparedStatementCreator(sql), pss);
}
protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss)
throws DataAccessException {
logger.debug("Executing prepared SQL update");
//---------
//标注三:这里才是真正执行我们sql的核心逻辑
return updateCount(execute(psc, ps -> {
//标注四:执行到ps对象回调重新定位到这里
try {
if (pss != null) {
pss.setValues(ps);
}
int rows = ps.executeUpdate();
if (logger.isDebugEnabled()) {
logger.debug("SQL update affected " + rows + " rows");
}
return rows;
}
finally {
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}));
}
通过上面代码咱们开始逐个分析咯~
标注1:通过追代码我们找到了这个方法的位置,发现通过构造创建ArgumentPreparedStatementSetter对象同时把具体的参数传入,传入后则进入标注二[Java] 纯文本查看 复制代码
protected PreparedStatementSetter newArgPreparedStatementSetter(@Nullable Object[] args) {
return new ArgumentPreparedStatementSetter(args);
}
标注2:这里只是进一步调用重载update方法,但是在这里需要注意的是由一个新的对象new SimplePreparedStatementCreator(sql)以及之前标注一的返回值作为参数pss进一步传递,ok,在这里我们又跳转到了SimplePreparedStatementCreator这个类SimplePreparedStatementCreator.java
[Java] 纯文本查看 复制代码
public SimplePreparedStatementCreator(String sql) {
Assert.notNull(sql, "SQL must not be null");
this.sql = sql;
}
看到这里,说白了就是把sql语句封装到SimplePreparedStatementCreator对象中,而该对想在创建时对sql语句进行了一个断言操作
标注3:到了这里我们才发现是真正对sql进行操作的部分.ok.开始分析[Java] 纯文本查看 复制代码
private static int updateCount(@Nullable Integer result) {
Assert.state(result != null, "No update count");
return result;
}
这里需要注意的是updateCount()这个方法只是为了对增删改操作后的结果进行一个校验,他本身没有去执行sql语句,核心业务逻辑在execute()方法中,当然这在我们看到了lambda表达式,但是我们真正关注的这个方法在执行的时候,把我们标注一的封装参数的对象和标注二的封装sql的对象都传入方法中,接下来咱们就开始来分析核心逻辑咯~
[Java] 纯文本查看 复制代码
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)
throws DataAccessException {
Assert.notNull(psc, "PreparedStatementCreator must not be null");
Assert.notNull(action, "Callback object must not be null");
if (logger.isDebugEnabled()) {
//-------
//标注4
String sql = getSql(psc);
logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));
}
//-------
//标注5
Connection con = DataSourceUtils.getConnection(obtainDataSource());
PreparedStatement ps = null;
try {
ps = psc.createPreparedStatement(con);
//-------
//标注6
applyStatementSettings(ps);
//-------
//标注7
T result = action.doInPreparedStatement(ps);
handleWarnings(ps);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
String sql = getSql(psc);
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());
}
}
标注4参考下图~
标注5这里没必要多解释,就是JDBC那一套嘛Connection con = DataSourceUtils.getConnection(obtainDataSource());
要执行sql第一件事就是得获取数据库连接对象嘛,当然咱们这里是通过DataSource获取的,所以Connection对象自然也是被增强过的~
ps = psc.createPreparedStatement(con);
这里的ps就是PreparedStatement对象,作用就是预编译sql的对象,主要的作用是提高sql执行效率和避免最基本的sql注入问题~
上面方法最终追到SimplePreparedStatementCreator
[Java] 纯文本查看 复制代码
private static class SimplePreparedStatementCreator implements PreparedStatementCreator, SqlProvider {
...
@Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
return con.prepareStatement(this.sql);
}
...
}
其实执行到这里我们就发现我们预编译sql对象已经创建出来了,但是在执行的时候我们还需要讲参数传入.所以后面的操作也应该能够猜到了~
标注6.applyStatementSettings(ps)追到方法中发现这里面都是在做一些配置信息,和执行sql相关的作用不大,咱们在这里则不过多关注[Java] 纯文本查看 复制代码
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());
}
标注7.到了这里才是我们真正要的结果,我们发现T result = action.doInPreparedStatement(ps); 真正执行sql语句的地方到了,但是这里result的类型是泛型,所以我们在这里发现其实我们CRUD操作其实最后都会走到这里,只不过我们这次不分析查询过程,所以基于,因未我们这里执行的增删改,所以返回值其实是int类型,返回的内容是影响的行数.所以我们主要关注的是这个方法具体的执行逻辑
这里的action对象是PreparedStatementCallback,但是我们发现这里其实是是一个回调对象,所以我们真正关注的就是PreparedStatementCallback里面的doInPreparedStatement方法,但是真正追进去才发现,在源码中我们从头到尾没有专门创建一个该接口的实现类
[Java] 纯文本查看 复制代码
public interface PreparedStatementCallback<T> {
@Nullable
T doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException;
}
可能纳闷了,没有实现类的话这个方法怎么能执行呢?而且从头到尾也没有看到有代理对象,ok,不卖关子了,其实还记得我们的标注3么?就是那个lamdba表达式么,其实我们那里就是具体实现,只不过是使用内部匿名类实现的而已~~
所以我们这里又追到了ArgumentPreparedStatementSetter的setValues()
[Java] 纯文本查看 复制代码
public void setValues(PreparedStatement ps) throws SQLException {
if (this.args != null) {
for (int i = 0; i < this.args.length; i++) {
Object arg = this.args;
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);
}
}
其实在这里我们就发现了,这里的作用其实就是用来将?占位符和传入的参数值进行匹配的,接下来的东西就是做匹配操作的,但是太复杂我就把没必要的省略了~直接带大家看下相关过程~
StatementCreatorUtils.setParameterValue(...)通过该方法我们追到
StatementCreatorUtils
[Java] 纯文本查看 复制代码
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);
}
其内部调用
[Java] 纯文本查看 复制代码
private static void setParameterValueInternal(PreparedStatement ps, int paramIndex, int sqlType,
@Nullable String typeName, @Nullable Integer scale, @Nullable Object inValue) throws SQLException {
...省略大量代码
if (inValueToUse == null) {
setNull(ps, paramIndex, sqlTypeToUse, typeNameToUse);
}
else {
setValue(ps, paramIndex, sqlTypeToUse, typeNameToUse, scale, inValueToUse);
}
}
注意最终调用setNull()或者setValue(),接下来核心逻辑都在setValue()中了
[Java] 纯文本查看 复制代码
private static void setValue(PreparedStatement ps, int paramIndex, int sqlType,
@Nullable String typeName, @Nullable Integer scale, Object inValue) throws SQLException {
if (inValue instanceof SqlTypeValue) {
((SqlTypeValue) inValue).setTypeValue(ps, paramIndex, sqlType, typeName);
}
else if (inValue instanceof SqlValue) {
((SqlValue) inValue).setValue(ps, paramIndex);
}
else if (sqlType == Types.VARCHAR || sqlType == Types.NVARCHAR ||
sqlType == Types.LONGVARCHAR || sqlType == Types.LONGNVARCHAR) {
ps.setString(paramIndex, inValue.toString());
}
else if ((sqlType == Types.CLOB || sqlType == Types.NCLOB) && isStringValue(inValue.getClass())) {
String strVal = inValue.toString();
if (strVal.length() > 4000) {
// Necessary for older Oracle drivers, in particular when running against an Oracle 10 database.
// Should also work fine against other drivers/databases since it uses standard JDBC 4.0 API.
if (sqlType == Types.NCLOB) {
ps.setNClob(paramIndex, new StringReader(strVal), strVal.length());
}
else {
ps.setClob(paramIndex, new StringReader(strVal), strVal.length());
}
return;
}
// Fallback: regular setString binding
ps.setString(paramIndex, strVal);
}
else if (sqlType == Types.DECIMAL || sqlType == Types.NUMERIC) {
if (inValue instanceof BigDecimal) {
ps.setBigDecimal(paramIndex, (BigDecimal) inValue);
}
else if (scale != null) {
ps.setObject(paramIndex, inValue, sqlType, scale);
}
else {
ps.setObject(paramIndex, inValue, sqlType);
}
}
else if (sqlType == Types.BOOLEAN) {
if (inValue instanceof Boolean) {
ps.setBoolean(paramIndex, (Boolean) inValue);
}
else {
ps.setObject(paramIndex, inValue, Types.BOOLEAN);
}
}
else if (sqlType == Types.DATE) {
if (inValue instanceof java.util.Date) {
if (inValue instanceof java.sql.Date) {
ps.setDate(paramIndex, (java.sql.Date) inValue);
}
else {
ps.setDate(paramIndex, new java.sql.Date(((java.util.Date) inValue).getTime()));
}
}
else if (inValue instanceof Calendar) {
Calendar cal = (Calendar) inValue;
ps.setDate(paramIndex, new java.sql.Date(cal.getTime().getTime()), cal);
}
else {
ps.setObject(paramIndex, inValue, Types.DATE);
}
}
else if (sqlType == Types.TIME) {
if (inValue instanceof java.util.Date) {
if (inValue instanceof java.sql.Time) {
ps.setTime(paramIndex, (java.sql.Time) inValue);
}
else {
ps.setTime(paramIndex, new java.sql.Time(((java.util.Date) inValue).getTime()));
}
}
else if (inValue instanceof Calendar) {
Calendar cal = (Calendar) inValue;
ps.setTime(paramIndex, new java.sql.Time(cal.getTime().getTime()), cal);
}
else {
ps.setObject(paramIndex, inValue, Types.TIME);
}
}
else if (sqlType == Types.TIMESTAMP) {
if (inValue instanceof java.util.Date) {
if (inValue instanceof java.sql.Timestamp) {
ps.setTimestamp(paramIndex, (java.sql.Timestamp) inValue);
}
else {
ps.setTimestamp(paramIndex, new java.sql.Timestamp(((java.util.Date) inValue).getTime()));
}
}
else if (inValue instanceof Calendar) {
Calendar cal = (Calendar) inValue;
ps.setTimestamp(paramIndex, new java.sql.Timestamp(cal.getTime().getTime()), cal);
}
else {
ps.setObject(paramIndex, inValue, Types.TIMESTAMP);
}
}
else if (sqlType == SqlTypeValue.TYPE_UNKNOWN || (sqlType == Types.OTHER &&
"Oracle".equals(ps.getConnection().getMetaData().getDatabaseProductName()))) {
if (isStringValue(inValue.getClass())) {
ps.setString(paramIndex, inValue.toString());
}
else if (isDateValue(inValue.getClass())) {
ps.setTimestamp(paramIndex, new java.sql.Timestamp(((java.util.Date) inValue).getTime()));
}
else if (inValue instanceof Calendar) {
Calendar cal = (Calendar) inValue;
ps.setTimestamp(paramIndex, new java.sql.Timestamp(cal.getTime().getTime()), cal);
}
else {
// Fall back to generic setObject call without SQL type specified.
ps.setObject(paramIndex, inValue);
}
}
else {
// Fall back to generic setObject call with SQL type specified.
ps.setObject(paramIndex, inValue, sqlType);
}
}
ok,到此结束我们也就发现最终参数的封装还是用PreparedStatement的setXXX()方法进行?站位符合具体参数的绑定,那么绑定完后,我们则又回到execute()方法的标注7了~ 而我们执行是增删改所以返回的是Integer型数据,最后执行handleWarnings(ps);
[Java] 纯文本查看 复制代码
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());
}
}
至于这个方法也就是对日志的记录和输出
做完了上述操作后,我们就进入到了finally代码块中 在这里面则是主要做一些请求和对对象回收的操作,好了,到此为止,我们则是把整个jdbctemplate的执行增删改的业务逻辑通过源码的形式带着大家进行梳理,不知道看的还过瘾不O(∩_∩)O~
作者: 一个人一座城0.0 时间: 2018-12-28 09:46
看一看。
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) |
黑马程序员IT技术论坛 X3.2 |