文章目录
- 一、目标:JDBC功能整合
- 二、设计:JDBC功能整合
- 三、实现:JDBC功能整合
- 3.1 工程结构
- 3.2 整合JDBC功能核心类图
- 3.3 数据源操作
- 3.3.1 数据源操作抽象类
- 3.3.2 JDBC 工具类
- 3.4 数据库执行
- 3.4.1 语句处理器接口
- 3.4.2 结果处理器接口
- 3.4.3 行转列结果处理器实现类
- 3.4.4 行映射器接口
- 3.4.5 行列Map映射器实现类
- 3.3.6 数据库执行接口
- 3.5 数据库操作模板
- 3.5.1 SQL提供者
- 3.5.2 JDBC操作抽象类
- 3.5.3 JDBC操作模板
- 四、测试:JDBC功能整合
- 4.1 添加测试配置
- 4.1.1 sql数据库表
- 4.1.2 Spring属性配置文件
- 4.1.3 初始化 Spring和JdbcTemplate对象
- 4.2 单元测试
- 4.2.1 插入测试
- 4.2.2 查询测试
- 五、总结:JDBC功能整合
一、目标:JDBC功能整合
💡 如何结合 Spring 框架,封装 JDBC 并对外提供统一的数据操作模板?
- JDBC 的封装主要实现的是
JdbcTemplate
的功能。Spring 对数据库的操作在 JDBC 上做了深层次的封装。使用 Spring 的注入功能,可以将DataSource
注册到JdbcTemplate
中使用。 JdbcTemplate
主要提供的方法。execute
方法:用于执行任何 SQL 语句,一般用于执行 DDL 语句。update
方法及batchUpdate
方法:update
方法用于执行新增、修改、删除等语句。batchUpdate
方法用于执行与批处理相关的语句。
query
方法及queryForXXX
方法:用于执行与查询相关的语句。call
方法:用于执行与存储过程、函数相关的语句。
二、设计:JDBC功能整合
💡 设计:整合 JDBC 服务
- Spring 框架将与 JDBC 的相关操作封装在
spring-jdbc
模块下的JdbcTemplate
类中调用,并结合 Spring 提供的InitializingBean
接口,在BeanFactory
设置属性后进行相应的自定义初始化处理,将 JDBC 整合到 Spring 框架中。
- DB 连接池提供数据源服务,这里将
DruidDataSource
作为连接池使用。 - 在引入连接池后,基于
JdbcTemplate
完成对数据库的操作处理,包括执行 SQL 语句(如查询、更新和删除数据库表等操作,以及开发对应的数据库表语句)。将执行后的结果进行封装,使用ResultSetExtractor
接口、RowMapper
接口转换数据类型。
三、实现:JDBC功能整合
3.1 工程结构
spring-step-18
|-src|-main| |-java| |-com.lino.springframework| |-aop| | |-aspectj| | | |-AspectJExpressionPointcut.java| | | |-AspectJExpressionPointcutAdvisor.java| | |-framework| | | |-adapter| | | | |-MethodBeforeAdviceInterceptor.java| | | |-autoproxy| | | | |-DefaultAdvisorAutoProxyCreator.java| | | |-AopProxy.java| | | |-Cglib2AopProxy.java| | | |-JdkDynamicAopProxy.java| | | |-ProxyFactory.java| | | |-ReflectiveMethodInvocation.java| | |-AdvisedSupport.java| | |-Advisor.java| | |-BeforeAdvice.java| | |-ClassFilter.java| | |-MethodBeforeAdvice.java| | |-MethodMatcher.java| | |-Pointcut.java| | |-PointcutAdvisor.java| | |-TargetSource.java| |-beans| | |-factory| | | |-annotation| | | | |-Autowired.java| | | | |-AutowiredAnnotationBeanPostProcessor.java| | | | |-Qualifier.java| | | | |-Value.java| | | |-config| | | | |-AutowireCapableBeanFactory.java| | | | |-BeanDefinition.java| | | | |-BeanFactoryPostProcessor.java| | | | |-BeanPostProcessor.java| | | | |-BeanReference.java| | | | |-ConfigurableBeanFactory.java| | | | |-InstantiationAwareBeanPostProcessor.java| | | | |-SingletonBeanRegistry.java| | | |-support| | | | |-AbstractAutowireCapableBeanFactory.java| | | | |-AbstractBeabDefinitionReader.java| | | | |-AbstractBeabFactory.java| | | | |-BeabDefinitionReader.java| | | | |-BeanDefinitionRegistry.java| | | | |-CglibSubclassingInstantiationStrategy.java| | | | |-DefaultListableBeanFactory.java| | | | |-DefaultSingletonBeanRegistry.java| | | | |-DisposableBeanAdapter.java| | | | |-FactoryBeanRegistrySupport.java| | | | |-InstantiationStrategy.java| | | | |-SimpleInstantiationStrategy.java| | | |-xml| | | | |-XmlBeanDefinitionReader.java| | | |-Aware.java| | | |-BeanClassLoaderAware.java| | | |-BeanFactory.java| | | |-BeanFactoryAware.java| | | |-BeanNameAware.java| | | |-ConfigurableListableBeanFactory.java| | | |-DisposableBean.java| | | |-FactoryBean.java| | | |-HierarcgicalBeanFactory.java| | | |-InitializingBean.java| | | |-ListableBeanFactory.java| | | |-ObjectFactory.java| | | |-PropertyPlaceholderConfigurer.java| | |-BeansException.java| | |-PropertyValue.java| | |-PropertyValues.java| |-context| | |-annotation| | | |-ClassPathBeanDefinitionScanner.java| | | |-ClassPathScanningCandidateComponentProvider.java| | | |-Scope.java| | |-event| | | |-AbstractApplicationEventMulticaster.java| | | |-ApplicationContextEvent.java| | | |-ApplicationEventMulticaster.java| | | |-ContextclosedEvent.java| | | |-ContextRefreshedEvent.java| | | |-SimpleApplicationEventMulticaster.java| | |-support| | | |-AbstractApplicationContext.java| | | |-AbstractRefreshableApplicationContext.java| | | |-AbstractXmlApplicationContext.java| | | |-ApplicationContextAwareProcessor.java| | | |-ClassPathXmlApplicationContext.java| | | |-ConversionServiceFactoryBean.java| | |-ApplicationContext.java| | |-ApplicationContextAware.java| | |-ApplicationEvent.java| | |-ApplicationEventPublisher.java| | |-ApplicationListener.java| | |-ConfigurableApplicationContext.java| |-core| | |-convert| | | |-converter| | | | |-Converter.java| | | | |-ConverterFactory.java| | | | |-ConverterRegistry.java| | | | |-GenericConverter.java| | | |-support| | | | |-DefaultConversionService.java| | | | |-GenericConversionService.java| | | | |-StringToNumberConverterFactory.java| | | |-ConversionService| | |-io| | | |-ClassPathResource.java| | | |-DefaultResourceLoader.java| | | |-FileSystemResource.java| | | |-Resource.java| | | |-ResourceLoader.java| | | |-UrlResource.java| |-jdbc| | |-core| | | |-ColumnMapRowMapper.java| | | |-JdbcOperations.java| | | |-JdbcTemplate.java| | | |-ResultSetExtractor.java| | | |-RowMapper.java| | | |-RowMapperResultSetExtractor.java| | | |-SqlProvider.java| | | |-StatementCallback.java| | |-datasource| | | |-DataSourceUtils.java| | |-support| | | |-JdbcAccessor.java| | | |-JdbcUtils.java| |-stereotype| | |-Component.java| |-util| | |-ClassUtils.java| | |-NumberUtils.java| | |-StringValueResolver.java|-test|-java|-com.lino.springframework.test|-ApiTest.java|-resources|-spring.xml
3.2 整合JDBC功能核心类图
DataSource
用于提供Connection
链接操作,后续还会扩展辅助类(ConnectionHandler
、ConnectionHolder
)与数据库事务结合。JdbcTemplate
是执行数据库操作的入口类,提供T execute(StatementCallback<T> action, boolean closeResources)
对数据库操作的实现。JdbcOperations
用于定义很多数据库操作,包括各类的查询处理。这些操作也会调用execute
方法进行处理,再对数据进行封装。- 封装操作就是使用
ResultSetExtractor
接口、RowMapper
接口进行数据类型转换的。
- 封装操作就是使用
3.3 数据源操作
3.3.1 数据源操作抽象类
DataSourceUtils.java
package com.lino.springframework.jdbc.datasource;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;/*** @description: 数据源操作抽象类*/
public abstract class DataSourceUtils {/*** 获取数据库连接** @param dataSource 数据库对象* @return 数据库连接*/public static Connection getConnection(DataSource dataSource) {try {return dataSource.getConnection();} catch (SQLException e) {throw new RuntimeException("Failed to obtain JDBC Connection", e);}}
}
DataSourceUtils
数据源的操作工具类提供了连接池的链接、关闭、释放等功能。这也是对 Spring 源码的简化。
3.3.2 JDBC 工具类
JdbcUtils.java
package com.lino.springframework.jdbc.support;import cn.hutool.core.util.StrUtil;
import java.sql.*;/*** @description: JDBC工具类*/
public class JdbcUtils {/*** Determine the column name to use. The column name is determined based on a* lookup using ResultSetMetaData.* <p>This method implementation takes into account recent clarifications* expressed in the JDBC 4.0 specification:* <p><i>columnLabel - the label for the column specified with the SQL AS clause.* If the SQL AS clause was not specified, then the label is the name of the column</i>.** @param resultSetMetaData the current meta-data to use* @param columnIndex the index of the column for the look up* @return the column name to use* @throws SQLException in case of lookup failure*/public static String lookupColumnName(ResultSetMetaData resultSetMetaData, int columnIndex) throws SQLException {String name = resultSetMetaData.getColumnLabel(columnIndex);if (StrUtil.isEmpty(name)) {name = resultSetMetaData.getColumnName(columnIndex);}return name;}/*** Retrieve a JDBC column value from a ResultSet, using the most appropriate* value type. The returned value should be a detached value object, not having* any ties to the active ResultSet: in particular, it should not be a Blob or* Clob object but rather a byte array or String representation, respectively.* <p>Uses the {@code getObject(index)} method, but includes additional "hacks"* to get around Oracle 10g returning a non-standard object for its TIMESTAMP* datatype and a {@code java.sql.Date} for DATE columns leaving out the* time portion: These columns will explicitly be extracted as standard* {@code java.sql.Timestamp} object.** @param rs is the ResultSet holding the data* @param index is the column index* @return the value object* @throws SQLException if thrown by the JDBC API* @see java.sql.Blob* @see java.sql.Clob* @see java.sql.Timestamp*/public static Object getResultSetValue(ResultSet rs, int index) throws SQLException {Object obj = rs.getObject(index);String className = null;if (null != obj) {className = obj.getClass().getName();}if (obj instanceof Blob) {Blob blob = (Blob) obj;obj = blob.getBytes(1, (int) blob.length());} else if (obj instanceof Clob) {Clob clob = (Clob) obj;obj = clob.getSubString(1, (int) clob.length());} else if ("oracle.sql.TIMESTAMP".equals(className) || "oracle.sql.TIMESTAMPTZ".equals(className)) {obj = rs.getTimestamp(index);} else if (null != className && className.startsWith("oracle.sql.DATE")) {String metadataClassName = rs.getMetaData().getColumnClassName(index);if ("java.sql.Timestamp".equals(metadataClassName) || "oracle.sql.TIMESTAMP".equals(metadataClassName)) {obj = rs.getTimestamp(index);} else {obj = rs.getDate(index);}} else if (obj instanceof Date) {if ("java.sql.Timestamp".equals(rs.getMetaData().getColumnClassName(index))) {obj = rs.getDate(index);}}return obj;}
}
3.4 数据库执行
3.4.1 语句处理器接口
StatementCallback.java
package com.lino.springframework.jdbc.core;import java.sql.SQLException;
import java.sql.Statement;/*** @description: 语句处理器*/
public interface StatementCallback<T> {/*** 执行语句** @param statement 语句对象* @return 泛型结果* @throws SQLException SQL异常*/T doInStatement(Statement statement) throws SQLException;
}
3.4.2 结果处理器接口
ResultSetExtractor.java
package com.lino.springframework.jdbc.core;import java.sql.ResultSet;
import java.sql.SQLException;/*** @description: 结果返回器*/
public interface ResultSetExtractor<T> {/*** 返回数据** @param rs 结果集* @return 泛型结果* @throws SQLException SQL异常*/T extractData(ResultSet rs) throws SQLException;
}
3.4.3 行转列结果处理器实现类
RowMapperResultSetExtractor.java
package com.lino.springframework.jdbc.core;import cn.hutool.core.lang.Assert;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;/*** @description: 行转列*/
public class RowMapperResultSetExtractor<T> implements ResultSetExtractor<List<T>> {private final RowMapper<T> rowMapper;private final int rowsExpected;public RowMapperResultSetExtractor(RowMapper<T> rowMapper) {this(rowMapper, 0);}public RowMapperResultSetExtractor(RowMapper<T> rowMapper, int rowsExpected) {Assert.notNull(rowMapper, "RowMapper is required");this.rowMapper = rowMapper;this.rowsExpected = rowsExpected;}@Overridepublic List<T> extractData(ResultSet rs) throws SQLException {List<T> results = this.rowsExpected > 0 ? new ArrayList<>(this.rowsExpected) : new ArrayList<>();int rowNum = 0;while (rs.next()) {results.add(this.rowMapper.mapRow(rs, rowNum++));}return results;}
}
3.4.4 行映射器接口
RowMapper.java
package com.lino.springframework.jdbc.core;import java.sql.ResultSet;
import java.sql.SQLException;/*** @description: 行映射器接口*/
public interface RowMapper<T> {/*** 返回行记录** @param rs 结果集对象* @param rowNum 返回的行数* @return 泛型结果* @throws SQLException SQL异常*/T mapRow(ResultSet rs, int rowNum) throws SQLException;
}
3.4.5 行列Map映射器实现类
ColumnMapRowMapper.java
package com.lino.springframework.jdbc.core;import com.lino.springframework.jdbc.support.JdbcUtils;import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.Map;/*** @description: 行列Map映射器*/
public class ColumnMapRowMapper implements RowMapper<Map<String, Object>> {@Overridepublic Map<String, Object> mapRow(ResultSet rs, int rowNum) throws SQLException {ResultSetMetaData rsMetaData = rs.getMetaData();int columnCount = rsMetaData.getColumnCount();Map<String, Object> mapOfColumnValues = createColumnMap(columnCount);for (int i = 1; i <= columnCount; i++) {String columnName = JdbcUtils.lookupColumnName(rsMetaData, i);mapOfColumnValues.putIfAbsent(getColumnKey(columnName), getColumnValue(rs, i));}return mapOfColumnValues;}protected Map<String, Object> createColumnMap(int columnCount) {return new LinkedHashMap<>(columnCount);}protected String getColumnKey(String columnName) {return columnName;}protected Object getColumnValue(ResultSet rs, int index) throws SQLException {return JdbcUtils.getResultSetValue(rs, index);}
}
3.3.6 数据库执行接口
JdbcOperations.java
package com.lino.springframework.jdbc.core;import java.util.List;
import java.util.Map;/*** @description: 数据库执行接口*/
public interface JdbcOperations {/*** 执行语句处理器** @param action 语句处理器* @param <T> 泛型* @return 泛型结果* @throws Exception 异常*/<T> T execute(StatementCallback<T> action) throws Exception;/*** 执行SQL语句** @param sql SQL语句*/void execute(String sql);/*** 执行查询** @param sql SQL语句* @param res 结果返回器* @param <T> 泛型* @return 泛型结果*/<T> T query(String sql, ResultSetExtractor<T> res);/*** 执行查询** @param sql SQL语句* @param rowMapper 行对象* @param <T> 泛型* @return 泛型集合结果*/<T> List<T> query(String sql, RowMapper<T> rowMapper);/*** 查询列表** @param sql SQL语句* @return Map集合*/List<Map<String, Object>> queryForList(String sql);
}
- 在 Spring JDBC 框架中,
JdbcOperations
的功能很简单,就是定义了一组用于 JDBC 操作的接口。
3.5 数据库操作模板
3.5.1 SQL提供者
SqlProvider.java
package com.lino.springframework.jdbc.core;/*** @description: SQL提供者*/
public interface SqlProvider {/*** 获取SQL语句** @return SQL语句*/String getSql();
}
3.5.2 JDBC操作抽象类
JdbcAccessor.java
package com.lino.springframework.jdbc.support;import cn.hutool.core.lang.Assert;
import com.lino.springframework.beans.factory.InitializingBean;
import javax.sql.DataSource;/*** @description: JDBC操作接口*/
public abstract class JdbcAccessor implements InitializingBean {private DataSource dataSource;public DataSource getDataSource() {return dataSource;}public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;}protected DataSource obtainDataSource() {DataSource dataSource = getDataSource();Assert.state(dataSource != null, "No DataSource set");return dataSource;}@Overridepublic void afterPropertiesSet() {if (getDataSource() == null) {throw new IllegalArgumentException("Property 'dataSource' is required");}}
}
3.5.3 JDBC操作模板
JdbcTemplate.java
package com.lino.springframework.jdbc.core;import cn.hutool.core.lang.Assert;
import com.lino.springframework.jdbc.datasource.DataSourceUtils;
import com.lino.springframework.jdbc.support.JdbcAccessor;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Map;/*** @description: JDBC 操作模板*/
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {/*** 查询大小*/private int fetchSize = -1;/*** 最大行数*/private int maxRows = -1;/*** 查询时间*/private int queryTimeout = -1;public JdbcTemplate() {}public JdbcTemplate(DataSource dataSource) {setDataSource(dataSource);afterPropertiesSet();}public int getFetchSize() {return fetchSize;}public void setFetchSize(int fetchSize) {this.fetchSize = fetchSize;}public int getMaxRows() {return maxRows;}public void setMaxRows(int maxRows) {this.maxRows = maxRows;}public int getQueryTimeout() {return queryTimeout;}public void setQueryTimeout(int queryTimeout) {this.queryTimeout = queryTimeout;}@Overridepublic <T> T execute(StatementCallback<T> action) {Connection con = DataSourceUtils.getConnection(obtainDataSource());try {Statement stmt = con.createStatement();applyStatementSettings(stmt);return action.doInStatement(stmt);} catch (SQLException ex) {throw new RuntimeException("StatementCallback", ex);}}@Overridepublic void execute(String sql) {class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider {@Overridepublic String getSql() {return sql;}@Overridepublic Object doInStatement(Statement statement) throws SQLException {statement.execute(sql);return null;}}execute(new ExecuteStatementCallback());}@Overridepublic <T> T query(String sql, ResultSetExtractor<T> res) {class QueryStatementCallback implements StatementCallback<T>, SqlProvider {@Overridepublic String getSql() {return sql;}@Overridepublic T doInStatement(Statement statement) throws SQLException {ResultSet rs = statement.executeQuery(sql);return res.extractData(rs);}}return execute(new QueryStatementCallback());}@Overridepublic <T> List<T> query(String sql, RowMapper<T> rowMapper) {return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));}@Overridepublic List<Map<String, Object>> queryForList(String sql) {return query(sql, new ColumnMapRowMapper());}private static <T> T result(T result) {Assert.state(null != result, "No result");return result;}protected void applyStatementSettings(Statement stat) throws SQLException {int fetchSize = getFetchSize();if (fetchSize != -1) {stat.setFetchSize(fetchSize);}int maxRows = getMaxRows();if (maxRows != -1) {stat.setMaxRows(maxRows);}}
}
JdbcTemplate
是对数据库操作的封装,在启动时由外部传入DataSource
数据源,这也是处理数据库操作最基本的方法。- 通过这样的封装,减少了用户操作的复杂性,也符合设计模式的原则。
execute
方法是整个数据库操作的核心方法,将同类的数据操作进行统一封装。一些个性化的操作需要进行回调处理。- 例如:在
JdbcTemplate#query
方法中,也是对JdbcTemplate#execute
进行包装操作,并返回处理结果。
- 例如:在
四、测试:JDBC功能整合
4.1 添加测试配置
4.1.1 sql数据库表
user.sql
CREATE TABLE `user` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增ID',`userId` varchar(9) DEFAULT NULL COMMENT '用户ID',`userHead` varchar(16) DEFAULT NULL COMMENT '用户头像',`createTime` datetime DEFAULT NULL COMMENT '创建时间',`updateTime` datetime DEFAULT NULL COMMENT '更新时间',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
4.1.2 Spring属性配置文件
spring.xml
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClass" value="com.mysql.cj.jdbc.Driver"/><property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring?useSSL=false&serverTimezone=Asia/Shanghai"/><property name="username" value="root"/><property name="password" value="123456"/></bean><bean id="jdbcTemplate" class="com.lino.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"/></bean></beans>
- 在
spring.xml
配置文件中,先配置数据库的链接信息及库表,再将dataSource
注入JdbcTemplate
中,由JdbcTemplate
完成数据库的操作。
4.1.3 初始化 Spring和JdbcTemplate对象
ApiTest.java
package com.lino.springframework.test;import com.lino.springframework.context.support.ClassPathXmlApplicationContext;
import com.lino.springframework.jdbc.core.JdbcTemplate;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
import java.util.Map;/*** @description: 测试类*/
public class ApiTest {private JdbcTemplate jdbcTemplate;@Beforepublic void init() {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);}
}
4.2 单元测试
4.2.1 插入测试
ApiTest.java
@Test
public void execute() {jdbcTemplate.execute("insert into user (id, userId, userHead, createTime, updateTime) values (1, '123456789', '01-50', now(), now())");
}
测试结果
信息: {dataSource-1} inited
4.2.2 查询测试
ApiTest.java
@Test
public void queryForList() {List<Map<String, Object>> allResult = jdbcTemplate.queryForList("select * from user");for (Map<String, Object> objectMap : allResult) {System.out.println("测试结果:" + objectMap);}
}
测试结果
信息: {dataSource-1} inited
测试结果:{id=1, userId=123456789, userHead=01-50, createTime=2022-12-08 14:39:15.0, updateTime=2022-12-08 14:39:15.0}
- 从测试结果看,这里已经把操作数据库的
JdbcTemplate
交由 Spring Bean 容器管理,并验证其数据库操作。
五、总结:JDBC功能整合
- 本节主要介绍了 Spring Bean 容器的扩展功能,可以在指定的任何阶段把需要交给 Spring 管理的对象进行初始化。
- 如:
InitializingBean
可以在BeanFactory
设置属性后进行相应的处理,整合其他对象。
- 如:
- 另外,需要熟悉 JDBC 的包装,使用支撑层
support
承接Bean
对象的扩展。DataSource
提供了操作数据源的功能,在core
包中完成对数据库的操作并返回相应的结果。