深入理解Spring(六)、JDBC源码分析
前言
深入理解Spring源码分为7小节,本小节为Spring源码第6小节,各个小节目录如下。
-
扫描过程 -
bean创建过程 -
容器扩展 -
AOP源码分析 -
事务源码分析
6. Spring JDBC源码分析
-
Spring常用设计模式
JDBC是Java的一套规范接口,用来和各种数据库交互,Java提供接口,各个厂商实现,但是里面并没有很厉害的东西可以让我们学习,最厉害的还是各个厂商的实现,但是他们的实现都很复杂,所以没有多少人去解析他们的源码,拿Mysql驱动来说,他需要创建Socket和目标服务器进行通信,我们每执行的sql语句,都会发送到远程服务端执行,执行后返回数据并转换成ResultSet,这其实是很复杂的事情,复杂的原因是要定义一套信息交互格式,虽然我们可以不用MySql驱动照样可以和Mysql服务端通信,但是这是很难的,如果你非常了解mysql交互协议的话,也并不是不可能。
其他厂商也是。
这里的驱动是指Driver类,任何厂商都会有实现,他是用来创建Connection的,但这里就有一个问题,Java是怎么知道使用哪个驱动的?
其实Java也不知道,在初学JDBC的时候,首先会使用Class.forName("com.mysql.cj.jdbc.Driver")
,这条语句其实是可以不需要的,因为有SPI机制,具体mysql在那个版本增加了对SPI的支持,我就不知道了,不想找资料了,但是8.0以后肯定是有的,在8.0后的驱动包中有META-INF/services/java.sql.Driver这个文件,这是关键,这个文件的内容是com.mysql.cj.jdbc.Driver。接下来调用ServiceLoader.load(Driver.class)会自动通过Class.forName加载这个类,Class.forName()一个类后,这个类的static代码块就会被执行。
那么驱动类被加载后往往都会在static代码块中执行DriverManager.registerDriver,注册的对象是本身,内部会有一个集合保存所有注册的Driver,接下来在调用getConnection()的时候,依次遍历已经注册后的Driver,看哪个Driver的connect()方法不返回null,表示这个Driver对传递的参数感兴趣,可以对他建立一个连接,所以每个厂商的url都不一样。
剩下的就是各个厂商对Statement的实现了。
下面还是分析一下JdbcTemplate。
JdbcTemplate的作用是省去了一些过程,直接扔给他一个DataSource就可以工作了,最重要的还为我们封装了对ResultSet的转换,但是可气的是,没有一个方法可以直接把结果转换成对应的实体,倒是提供了一大堆接口,通过接口,我们可以间接实现,这方面大不如Mybatis方便。
实现对List的转换
JdbcTemplate提供的queryForList()方法,没有一个可以把多行结果转换成对应的List
System.out.println(jdbcTemplate.queryForList("select id from test where id =1 ", Integer.class));
虽然他可以获取全部,但是返回的格式是List<Map<String, Object>>
下面我们做一下扩展。
public class EnhanceJdbcTemplate extends JdbcTemplate {
public EnhanceJdbcTemplate(DataSource dataSource) {
super(dataSource);
}
@Override
public <T> List<T> queryForList(String sql, Class<T> tClass) {
return this.query(sql, (rs, rowNum) -> mapping(tClass, rs));
}
private <T> T mapping(Class<T> cls, ResultSet resultSet) {
Field[] classFields = cls.getDeclaredFields();
T classInstance = BeanUtils.instantiateClass(cls);
for (Field classField : classFields) {
classField.setAccessible(true);
String name = classField.getName();
try {
Object dataBaseValue = resultSet.getObject(name, classField.getType());
ReflectionUtils.setField(classField, classInstance, dataBaseValue);
} catch (SQLException e) {
e.printStackTrace();
}
}
return classInstance;
}
}
EnhanceJdbcTemplate jdbcTemplate = new EnhanceJdbcTemplate(dataSource());
System.out.println(jdbcTemplate.queryForList("select * from test",Test.class));
源码分析
JdbcTemplate中有下面这几个接口很重要
RowMapper:提供给我们可以自行实现ResultSet到实体类的转换,这里的ResultSet是单条记录,虽然可以调用next,但是后续会被影响,JdbcTemplate内部会遍历ResultSet依次传递给他。
ResultSetExtractor:提供给我们可以自行实现ResultSet到实体的转换,这里的ResultSet是数据整体,通常转换成List。
RowCallbackHandler:提供给我们用于处理当前行,但是不返回数据,JdbcTemplate在内部还是遍历ResultSet,传递当前行给他。
query()
下面我们来分析一下query()源码,根据以前看Spring核心的经验,JdbcTemplate的作者和Spring 核心的作者绝逼不是一波人,风格大不相同,方法中都出现类定义了,虽然这是允许的,但很少见。
在query方法中,
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
@Override
@Nullable
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
try {
rs = stmt.executeQuery(sql);
return rse.extractData(rs);
}
finally {
JdbcUtils.closeResultSet(rs);
}
}
@Override
public String getSql() {
return sql;
}
}
return execute(new QueryStatementCallback(), true);
}
在进入到这里,这里会调用上面方法的doInStatement,执行executeQuery(),这个方法就到头了,剩下的是解析。
private <T> T execute(StatementCallback<T> action, boolean closeResources) throws DataAccessException {
Connection con = DataSourceUtils.getConnection(obtainDataSource());
Statement stmt = null;
try {
stmt = con.createStatement();
applyStatementSettings(stmt);
T result = action.doInStatement(stmt);
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
}
}
解析数据在这里,同样是要遍历ResultSet,然后通过rowMapper.mapRow把当前行转换成对应的实体了,这个通常我们自行实现。
@Override
public 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;
}
不得不说,JdbcTemplate这个封装的真不错。
其他updata()最终还是要调用到execute,只不过对应的StatementCallback实现类不同,但是看完他的源码,发现在方法中声明类是个很不错的选择,因为这个类只会在当前方法中使用,其他地方用不到。
实战
假定现在有如下接口。
public interface UserMapping {
List<Test> queryUser(Integer id);
}
我们想这样作,该如何实现?
@Configuration
public class UserTest implements InitializingBean {
@Autowired
UserMapping userMapping;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println(userMapping.queryUser(1));
}
}
先实现一个ImportBeanDefinitionRegistrar,用于把某个包下所有 标有@Mapping的接口注册到容器。
public class EasyMappingBeanDefinitionRegistry implements ImportBeanDefinitionRegistrar {
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<String, Object> mappingScan = importingClassMetadata.getAnnotationAttributes(EnabledEasyDB.class.getName());
PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();
String basePackage = ClassUtils.convertClassNameToResourcePath(((String) mappingScan.get("value")));
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + basePackage + '/' + DEFAULT_RESOURCE_PATTERN;
try {
Resource[] resources = pathMatchingResourcePatternResolver.getResources(packageSearchPath);
CachingMetadataReaderFactory cachingMetadataReaderFactory = new CachingMetadataReaderFactory();
for (Resource resource : resources) {
MetadataReader metadataReader = cachingMetadataReaderFactory.getMetadataReader(resource);
ClassMetadata classMetadata = metadataReader.getClassMetadata();
boolean anInterface = classMetadata.isInterface() &&
!classMetadata.isAnnotation() &&
metadataReader.getAnnotationMetadata().hasAnnotation(Mapping.class.getName());
if (anInterface) {
String className = metadataReader.getClassMetadata().getClassName();
AbstractBeanDefinition beanDefinition;
beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(MappingFactorBean.class)
.addConstructorArgValue(className)
.getBeanDefinition();
String beanName = className.substring(className.lastIndexOf(".") + 1);
registry.registerBeanDefinition(beanName, beanDefinition);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Import
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EasyMappingBeanDefinitionRegistry.class)
public @interface EnabledEasyDB {
String value() default "";
}
扩展JdbcTemplate源码,主要重写queryForList的两个重载,这样的返回更符合我们的习惯。
public class EnhanceJdbcTemplate extends JdbcTemplate {
public EnhanceJdbcTemplate(DataSource dataSource) {
super(dataSource);
}
@Override
public <T> List<T> queryForList(String sql, Class<T> elementType) {
return this.query(sql, (rs, rowNum) -> mapping(elementType, rs));
}
@Override
public <T> List<T> queryForList(String sql, Class<T> elementType, Object... args) throws DataAccessException {
return this.query(sql, (rs, rowNum) -> mapping(elementType, rs), args);
}
private <T> T mapping(Class<T> elementType, ResultSet resultSet) {
Field[] classFields = elementType.getDeclaredFields();
T classInstance = BeanUtils.instantiateClass(elementType);
for (Field classField : classFields) {
classField.setAccessible(true);
String name = classField.getName();
try {
Object dataBaseValue = resultSet.getObject(name, classField.getType());
ReflectionUtils.setField(classField, classInstance, dataBaseValue);
} catch (SQLException e) {
e.printStackTrace();
}
}
return classInstance;
}
}
@Mapping用于标在接口类上,使这个接口可以有代理。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Mapping {
}
为接口代理并放在容器,必须实现FactoryBean接口。
public class MappingFactorBean<T> implements FactoryBean<T>, BeanFactoryAware {
private BeanFactory beanFactory;
private Class<T> targetClass;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
public MappingFactorBean(String className) {
try {
targetClass = (Class<T>) Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
@Override
public T getObject() throws Exception {
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{targetClass},
new MappingInvocationHandler(beanFactory.getBean(EnhanceJdbcTemplate.class)));
}
@Override
public Class<?> getObjectType() {
return targetClass;
}
}
下面两个注解用于表在接口方法上。
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Select {
String value() default "";
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Update {
String value() default "";
}
最后UserMapping就可以这样做。
@Mapping
public interface UserMapping {
@Select("select * from test where id =?")
List<Test> queryUser(Integer id);
}
核心实现如下。
public class MappingInvocationHandler implements InvocationHandler {
private JdbcTemplate jdbcTemplate;
public MappingInvocationHandler(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass().equals(Object.class)) {
return method.invoke(this, args);
}
Class<?> methodReturnType = method.getReturnType();
if (methodReturnType.equals(List.class)) {
return resultForList(method, args);
}
return null;
}
public List<?> resultForList(Method method, Object[] args) {
String execSql = getSqlValueForMethodAnnotation(method);
ResolvableType resolvableType = ResolvableType.forMethodReturnType(method);
Class<?> rawClass = resolvableType.getGeneric(0).getRawClass();
if (args == null || args.length == 0) {
return jdbcTemplate.queryForList(execSql, rawClass);
}
return jdbcTemplate.queryForList(execSql, rawClass, args);
}
private String getSqlValueForMethodAnnotation(Method method) {
if (hasAnnotation(method, Select.class)) {
return AnnotationUtils.getAnnotation(method, Select.class).value();
}
if (hasAnnotation(method, Update.class)) {
return AnnotationUtils.getAnnotation(method, Update.class).value();
}
throw new IllegalArgumentException("找不到sql");
}
private boolean hasAnnotation(Method method, Class<? extends Annotation> annotation) {
return AnnotationUtils.getAnnotation(method, annotation) != null;
}
}
最后别忘了加数据源。
@Configuration
public class UserTest implements InitializingBean {
@Autowired
UserMapping userMapping;
@Bean
public DataSource dataSource() {
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource("jdbc:mysql://localhost:3306/test");
driverManagerDataSource.setUsername("root");
driverManagerDataSource.setPassword("hxl..");
driverManagerDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
return driverManagerDataSource;
}
@Bean
public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
return dataSourceTransactionManager;
}
@Bean
public EnhanceJdbcTemplate jdbcTemplate() {
return new EnhanceJdbcTemplate(dataSource());
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println(userMapping.queryUser(1));
}
}
- END -
原文始发于微信公众号(十四个字节):深入理解Spring(六)、JDBC源码分析