您当前的位置: 首页 > 

恐龙弟旺仔

暂无认证

  • 1浏览

    0关注

    282博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

JdbcTemplate源码解析

恐龙弟旺仔 发布时间:2018-06-26 14:12:25 ,浏览量:1

1.原始实现

    在jdbcTemplate之前,我们都是使用原始的方式来实现jdbc的相关操作,下面先看下原始的操作步骤

String url = "jdbc:mysql://localhost:3306/test";
		String username = "root";
		String password = "root";
		String sql = "update blog set name = ? where id=?";
		
		try {
			// 1.获取连接
			Connection connection = DriverManager.getConnection(url, username, password);
			
			// 2.创建 preparedStatement
			PreparedStatement prepareStatement = connection.prepareStatement(sql);
			
			// 3.初始化参数
			prepareStatement.setString(1, "lucy");
			prepareStatement.setInt(2, 1);
			
			// 4.执行update
			prepareStatement.executeUpdate();
			
		} catch (SQLException e) {
			e.printStackTrace();
		}

    主要分四步:获取连接、创建preparedStatement、封装参数、执行

    实际jdbcTemplate的实现也是按照这个步骤来的,只不过封装的更好

 

2.jdbcTemplate的实现

    1)创建JdbcConfig类,用于创建DataSource、JdbcTemplate

@Configuration
@ComponentScan(basePackages={"jdbc"})
public class JdbcConfig {

	@Bean
	public JdbcTemplate jdbcTemplate(DataSource dataSource){
		return new JdbcTemplate(dataSource);
	}
	
	@Bean
	public DataSource dataSource(){
		try {
			return new SimpleDriverDataSource(new com.mysql.jdbc.Driver(), "jdbc:mysql://localhost:3306/test", "root", "root");
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}
}

    2)创建JdbcOper类,用于具体实现

@Component
public class JdbcOper {

	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	public void query(){
		String sql = "select id,name,url from blog";
		
		jdbcTemplate.query(sql, new ResultSetExtractor() {
			@Override
			public Blog extractData(ResultSet rs) throws SQLException, DataAccessException {
				while(rs.next()){
					int id = rs.getInt("id");
					String name = rs.getString("name");
					System.out.println(id+":"+name);
				}
				return null;
			}
		});
		
		/*Blog blog = jdbcTemplate.queryForObject(sql, Blog.class);
		System.out.println(blog);*/
	}
	
	public void update(){
		
		String sql = "update blog set name = ? where id=?";
		jdbcTemplate.update(sql, new Object[]{"jack",1});
	}
}

    3)创建Test类,用于测试

	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JdbcConfig.class);
		JdbcOper bean = context.getBean(JdbcOper.class);
//		bean.query();
		bean.update();
	}

3.jdbcTemplate源码解析

    笔者使用的版本为spring-jdbc-4.3.8.RELEASE.jar

 

    jdbcTemplate.update方法主要代码如下

	protected int update(final PreparedStatementCreator psc, final PreparedStatementSetter pss)
			throws DataAccessException {

		logger.debug("Executing prepared SQL update");
		return execute(psc, new PreparedStatementCallback() {
			@Override
			public Integer doInPreparedStatement(PreparedStatement ps) throws SQLException {
				try {
					if (pss != null) {
                    // 3.初始化参数
						pss.setValues(ps);
					}
                    
                    // 4.执行update
					int rows = ps.executeUpdate();
					if (logger.isDebugEnabled()) {
						logger.debug("SQL update affected " + rows + " rows");
					}
					return rows;
				}
				finally {
					if (pss instanceof ParameterDisposer) {
						((ParameterDisposer) pss).cleanupParameters();
					}
				}
			}
		});
	}

    经过两步的对SQL和参数的封装之后,直接到该方法,最终执行execute方法,execute方法的第二个参数为一个回调函数,最终的执行会到这,下面看一下execute方法

	public  T execute(PreparedStatementCreator psc, PreparedStatementCallback action)
			throws DataAccessException {
    ...
        // 1.获取连接
		Connection con = DataSourceUtils.getConnection(getDataSource());
		PreparedStatement ps = null;
		try {
			Connection conToUse = con;
			if (this.nativeJdbcExtractor != null &&
					this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) {
				conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
			}
            // 2.创建 preparedStatment
			ps = psc.createPreparedStatement(conToUse);
			applyStatementSettings(ps);
			PreparedStatement psToUse = ps;
			if (this.nativeJdbcExtractor != null) {
				psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps);
			}
            
            // 3.直接调用action回调方法,参考上update() 剩余两步的封装参数和执行update都在回调方法中执行
			T result = action.doInPreparedStatement(psToUse);
			handleWarnings(ps);
			return result;
		}

下面逐步解析这四步操作:

    1)get connection

    进入DataSourceUtils类进行查看,可看到如下代码

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
		...
        logger.debug("Fetching JDBC Connection from DataSource");
    
        // 真正的实现也就是这一句
		Connection con = dataSource.getConnection();
        ...
		return con;
	}

    2)create preparedStatement

    默认使用的是SimplePreparedStatementCreator类,直接查看该类的实现

	private static class SimplePreparedStatementCreator implements PreparedStatementCreator, SqlProvider {

		private final String sql;

		public SimplePreparedStatementCreator(String sql) {
			Assert.notNull(sql, "SQL must not be null");
			this.sql = sql;
		}

		@Override
		public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
            // 真正实现的就是这句话
			return con.prepareStatement(this.sql);
		}

		@Override
		public String getSql() {
			return this.sql;
		}
	}

    3)init parm

    封装参数的过程在update的回调函数中,也就是PreparedStatementCallback

protected int update(final PreparedStatementCreator psc, final PreparedStatementSetter pss)
			throws DataAccessException {

		logger.debug("Executing prepared SQL update");
		return execute(psc, new PreparedStatementCallback() {
			@Override
			public Integer doInPreparedStatement(PreparedStatement ps) throws SQLException {
				try {
					if (pss != null) {
                        // 也就是这句话
						pss.setValues(ps);
					}
					...
				}
				...
			}
		});
	}

    可知默认使用的是newArgPreparedStatementSetter的setValues实现,查看该类实现如下

	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);
		}
	}

    继续跟踪,最终实现在StatementCreatorUtils类中的setValue方法中

 private static void setValue(PreparedStatement ps, int paramIndex, int sqlType, String typeName,
            Integer scale, Object inValue) throws SQLException {}

具体实现为:
            if (isStringValue(inValue.getClass())) {
				ps.setString(paramIndex, inValue.toString());
			}
            else {
				// Fall back to generic setObject call without SQL type specified.
				ps.setObject(paramIndex, inValue);
			}

   至此,对preparedStatement参数的封装结束

 

    4)execute update

    也就是回调函数中的

protected int update(final PreparedStatementCreator psc, final PreparedStatementSetter pss)
			throws DataAccessException {

		logger.debug("Executing prepared SQL update");
		return execute(psc, new PreparedStatementCallback() {
			@Override
			public Integer doInPreparedStatement(PreparedStatement ps) throws SQLException {
				try {
					...
                    // 直接执行update操作
					int rows = ps.executeUpdate();
					if (logger.isDebugEnabled()) {
						logger.debug("SQL update affected " + rows + " rows");
					}
					return rows;
				}
				...
			}
		});
	}

至此,update的源码分析解析,而至于其他操作,基本类似,也都是在回调函数中执行真正的execute操作,而对获取连接、封装参数等行为则抽象为公共方法

使用这种方式的好处就是,用户可以不关心公共行为,而只关心SQL及结果封装即可,这就是这种模板模式的好处

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

关注
打赏
1655041699
查看更多评论
立即登录/注册

微信扫码登录

0.0440s