JDBC问题分析
存在的问题
- 数据库配置信息存在硬编码问题。
- 频繁创建释放数据库连接.
- sql语句,设置参数,获取结果参数均存在硬编码问题。
- 手动封装返回结果集,较为繁琐。
解决方案
-
通过配置文件
- 连接池
- sql配置文件
- 反射,内省
自定义持久层框架思路分析
本质上是对JDBC的代码进行封装
1.思路
客户端(使用端)
-
引入自定义持久框架的jar包
-
客户端提供两部分配置信息:
- 数据库配置信息:驱动类,数据库连接,账户和密码
- sql配置信息:sql语句、参数类型、返回值类型
使用配置文件提供这两部分信息:
(1)SqlMapConfiguration.xml:存放数据库配置信息。存放mapper.xml的全路径(方便后期加载配置文件时只加载一次)
(2)mapper.xml:存在sql配置信息
- 客户端获取到配置文件输入流(Resources),并使用SqlSessionFactoryBuilder获取SqlSession执行查询
自定义持久层框架本身
-
加载配置文件:根据配置文件的路径,加载配置文件成字节输入流,存储到内存中
- 创建类Resources类:InputSteam getResourceAsSteam(String path)
-
创建两个java类(容器对象),存放的就是对配置文件解析出来的内容
- Configuration:核心配置文件,存在sqlMapConfig.xml解析出来的内容
- MappedStatement:映射配置类,存放mapper.xml解析出来的内容
-
解析配置文件:dom4j
- 创建类:SqlSessionFactoryBuilder:build(InputSteam in)
- 使用dom4j解析配置文件,将解析出来的内容,封装到容器对象中
- 创建SqlSessionFactory对象,生产SqlSession会话对象(工厂模式)
- 创建类:SqlSessionFactoryBuilder:build(InputSteam in)
-
创建SqlSessionFactory接口和实现类 DefaultSqlSessionFactory
- 生产sqlSession方法:openSession()
-
创建SqlSession接口和实现DefaultSqlSession
- 数据库的增删改成都会封装到这里
- 定义对数据库的curd操作:
- list()
- get()
- update()
- delete()
-
创建Executor接口及实现类DefaultExecutor实现类
- query(Configuration config,MappedStatement statement,Object... params):执行的是JDBC的代码
2.客户端使用的流程
1.客户端配置
pom.xml
<dependency>
<groupId>com.otoomo</groupId>
<artifactId>persistent</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
SqlMapConfiguration.xml
<configuration>
<!--
数据库配置
-->
<datasouce>
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql:///persistent_test?useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</datasouce>
<!--
sql配置路径
-->
<mapper resource="UserMapper.xml"/>
</configuration>
UserMapper.xml
- 自定义持久框架能定位到具体需要执行的sql的规则是根据
statementId来定位的。 - 那
statememtId的规则则是由以下部分组成的:namespace.id(命名空间+"."+sql配置的id)namespace: UserDao的包名全路径id: sql配置的唯一标识,也就是UserDao中对应的方法名称 - 在SqlSession中,会通过当前执行的DAO对象的类名+方法名,拼装成
statememtId,然后通过JDK动态代理来执行的
<mapper namespace="com.otoomo.dao.UserDao">
<!--查询所有用户-->
<select id="queryAll" resultType="com.otoomo.pojo.User" parameterType="com.otoomo.pojo.User">
SELECT * FROM user;
</select>
<!--根据ID获取用户-->
<select id="getById" resultType="com.otoomo.pojo.User" parameterType="java.lang.Integer">
SELECT * FROM user WHERE id = #{id};
</select>
<select id="getByIdAndName" resultType="com.otoomo.pojo.User" parameterType="java.util.Map">
SELECT * FROM user WHERE id = #{id} AND username = #{username};
</select>
<!--添加用户-->
<insert id="add" parameterType="com.otoomo.pojo.User">
insert into user (`username`) values(#{username})
</insert>
<!--删除用户-->
<delete id="delete">
delete from user where id = #{id}
</delete>
<!--更新用户-->
<update id="update" parameterType="com.otoomo.pojo.User">
update user set username=#{username} where id = #{id}
</update>
</mapper>
2.代码应用
public class TestApplication {
UserMapper userMapper;
@Before
public void initUserMapper() throws ParserException {
//获取配置输入流
InputStream inputStream = Resources.getResourceAsSteam("sqlMapConfiguration.xml");
//获取sql会话
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
userMapper = sqlSession.getMapper(UserMapper.class);
}
@Test
public void addUser() {
User user = new User();
user.setUsername("zhangsan");
userMapper.add(user);
//获取所有用户
List<User> users = userMapper.queryAll();
System.out.println(users);
}
@Test
public void update() {
int id = 15;
User updateUser = new User();
updateUser.setId(id);
updateUser.setUsername("张三");
userMapper.update(updateUser);
//获取用户
User user = userMapper.getById(id);
System.out.println(user);
}
@Test
public void delete() {
int delete = userMapper.delete(16);
System.out.println(delete);
}
@Test
public void getByIdAndName() {
User user = userMapper.getByIdAndName(17, "Tom");
System.out.println(user);
}
}
3.实现步骤
maven依赖配置文件:
<dependencies>
<!--数据库相关-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.17</version>
</dependency>
<!--连接池-->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!--END 数据库相关-->
<!--dom4j xml解析包-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!--dom4j使用selectNodes时,使用Xpath来定位元素,所以需要添加jaxen-->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
1. 创建Resources类
Resources 用于获取文件配置信息,返回输入流给调用方,后续通过dom4j或者其他方式解析对应配置
public class Resources {
private Resources() {
}
/**
* 获取指定路径的资源InputStream
*/
public static InputStream getResourceAsSteam(String path) {
InputStream resourceInputStream = ClassLoader.getSystemResourceAsStream(path);
return resourceInputStream;
}
}
2.创建两个容器类
MappedStatement
Sql配置映射容器类,存放的数据就是mapper.xml里面的配置信息。一个配置项目,就是一个 MappedStatement对象
public class MappedStatement {
/**
* sql唯一id标识
*/
private String id;
/**
* 返回值类型
*/
private String resultType;
/**
* 参数值类型
*/
private String paramterType;
}
Configuration 核心配置容器类
数据库配置信息容器;由于后续MappedStatement和DataSource都要经常获取。
为了减少多个参数的传递,可以把 MappedStatement 的信息,存放到核心配置容器mappedStatementMap中。
这样后面只需要传递Configuration参数就可以了
public class Configuration {
/**
* 存储数据库配置信息对象
*/
private DataSource dataSource;
/**
* sql配置映射信息Map
* key: statementId
* value: 封装好的sql配置映射类
* <p>
* statementId 为mapper.xml中的命名空间namespace加上sql配置映射的唯一id标识
* statementId = namespace.id
*/
private Map<String, MappedStatement> mappedStatementMap = new HashMap<>();
}
3.解析核心配置
这里通过dom4j解析文件,核心配置文件为:SqlMapConfiguration.xml
分为3个步骤:
- 创建XmlDataSourceParser解析器, 用于解析的数据库配置信息
- 创建XmlMapperParser解析器,用于解析mapper.xml
- 创建核心XmlConfigurationParser解析器,用于调用XmlDataSourceParser,XmlMapperParser两个解析器
public interface Parser<I> {
<T> T parse(I parseObj) throws ParserException;
}
1.创建XmlDataSourceParser解析器
解析数据库配置。获取到Configuration容器的dataSource
<datasource>
<property name="driviceClass" value="..."/>
</datasource>
public class XmlDataSourceConfigParser implements Parser<Document> {
private Configuration configuration;
public XmlDataSourceConfigParser(Configuration configuration) {
this.configuration = configuration;
}
@Override
public DataSource parse(Document document) throws ParserException {
try {
//<configuration>
Element rootElement = document.getRootElement();
//获取所有property的配置项
List<Element> propertyElements = rootElement.selectNodes("//property");
//获取所有property的属性值
Properties properties = new Properties();
propertyElements.forEach(element -> {
String name = element.attributeValue("name");
String value = element.attributeValue("value");
properties.put(name, value);
});
//通过配置信息,获取数据源
DataSource dataSource = buildDataSource(properties);
configuration.setDataSource(dataSource);
} catch (DataSourceConfigParamException | PropertyVetoException e) {
throw new ParserException(e.getMessage(), e);
}
return null;
}
/**
* 构建数据源
*
* @param properties
* @return
* @throws DataSourceConfigParamException
* @throws PropertyVetoException
*/
private DataSource buildDataSource(Properties properties) throws DataSourceConfigParamException, PropertyVetoException {
//检查数据库核心配置是否合法
checkDataSourcePropertysValidity(properties);
String driverClass = properties.getProperty("driverClass");
String jdbcUrl = properties.getProperty("jdbcUrl");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
//TODO 判空处理
//获取到配置信息后,创建数据库连接池
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass(driverClass);
comboPooledDataSource.setJdbcUrl(jdbcUrl);
comboPooledDataSource.setUser(username);
comboPooledDataSource.setPassword(password);
//TODO 完善更多参数配置
return comboPooledDataSource;
}
/**
* 检查数据库配置信息是否合法
*
* @param properties
* @throws DataSourceConfigParamException
*/
private void checkDataSourcePropertysValidity(Properties properties) throws DataSourceConfigParamException {
String driverClass = properties.getProperty("driverClass");
String jdbcUrl = properties.getProperty("jdbcUrl");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
if (StringUtils.isNullOrEmpty(driverClass)) {
throw new DataSourceConfigParamException("driverClass");
}
if (StringUtils.isNullOrEmpty(jdbcUrl)) {
throw new DataSourceConfigParamException("jdbcUrl");
}
if (StringUtils.isNullOrEmpty(username)) {
throw new DataSourceConfigParamException("username");
}
if (StringUtils.isNullOrEmpty(password)) {
throw new DataSourceConfigParamException("password");
}
}
}
2.创建XmlMapperParser解析器
用于解析的mapper.xml的文件路径和具体mapper.xml文件,并保存到MappedMatement容器中。
并把MappedMatement保存到Configuration容器的mappedStatementMap中
<mapper resource="UserMapper.xml" />
...
public class XmlMapperParser implements Parser<InputStream> {
private Configuration configuration;
public XmlMapperParser(Configuration configuration) {
this.configuration = configuration;
}
/**
* 解析mapper文件
*
* @param inputStream
* @return
*/
public MappedStatement parse(InputStream inputStream) throws ParserException {
try {
Document document = new SAXReader().read(inputStream);
//<mapper>
Element rootElement = document.getRootElement();
//select,update,insert,delete 语句的处理
handlerSelectElements(rootElement);
handlerUpdateElements(rootElement);
handlerInsertElements(rootElement);
handlerDeleteElements(rootElement);
} catch (DocumentException e) {
throw new ParserException(e.getMessage(), e);
}
return null;
}
private void handlerSelectElements(Element rootElement) {
handlerElements(rootElement, "select", SqlCommandType.SELECT);
}
private void handlerDeleteElements(Element rootElement) {
handlerElements(rootElement, "delete", SqlCommandType.DELETE);
}
private void handlerInsertElements(Element rootElement) {
handlerElements(rootElement, "insert", SqlCommandType.INSERT);
}
private void handlerUpdateElements(Element rootElement) {
handlerElements(rootElement, "update", SqlCommandType.UPDATE);
}
/**
* 处理sql配置
*
* @param rootElement
* @param type
* @param sqlCommandType
*/
private void handlerElements(Element rootElement, String type, SqlCommandType sqlCommandType) {
//命名空间
String namespace = rootElement.attributeValue("namespace");
List<Element> elementList = rootElement.selectNodes("//" + type);
elementList.forEach(element -> {
String id = element.attributeValue("id");
String resultType = element.attributeValue("resultType");
String parameterType = element.attributeValue("parameterType");
String sql = element.getTextTrim();
MappedStatement mappedStatement = new MappedStatement(id, resultType, parameterType, sql, sqlCommandType);
configuration.getMappedStatementMap().put(namespace + "." + id, mappedStatement);
});
}
}
- SqlCommandType用于记录sql映射的类型
public enum SqlCommandType {
INSERT, //插入
UPDATE, //更新
DELETE, //删除
SELECT; //查询
private SqlCommandType() {
}
}
3.创建核心XmlConfigurationParser解析器
用于调用XmlDataSourceParser,XmlMapperParser两个解析器,得到DataSource和mappedStatementMap的数据。
<configuration>
<datasource >
<property name="" value=""/>
</datasource>
<mapper resource=""/>
</configuration>
public class XmlConfigurationParser implements Parser<InputStream> {
private Configuration configuration;
public XmlConfigurationParser() {
configuration = new Configuration();
}
@Override
public Configuration parse(InputStream inputStream) throws ParserException {
try {
Document document = new SAXReader().read(inputStream);
/*
解析数据库配置
*/
Parser xmlDataSourceConfigParser = new XmlDataSourceConfigParser(configuration);
xmlDataSourceConfigParser.parse(document);
/*
解析mapper配置
*/
Parser xmlMapperParser = new XmlMapperParser(configuration);
//<configuration> 根元素
Element rootElement = document.getRootElement();
//获取到所有mapper元素
List<Element> mapperElements = rootElement.selectNodes("//mapper");
for (Element mapperPathElement : mapperElements) {
//获取具体的mapper.xml文件路径
String mapperPath = mapperPathElement.attributeValue("resource");
InputStream mapperInputStream = Resources.getResourceAsSteam(mapperPath);
//开始解析mapper.xml文件
xmlMapperParser.parse(mapperInputStream);
}
return configuration;
} catch (DocumentException e) {
throw new ParserException(e.getMessage(), e);
}
}
}
4.创建SqlSession
分为3个步骤:
- 创建SqlSession接口,并实现DefaultSqlSession
- 创建SqlSessionFactory工厂类,用于生产SqlSession
- 创建SqlSessionFactoryBuilder,用于创建SqlSessionFactory
这里会涉及到Executor的执行器类。会在第五步去实现,这里我们先实现SqlSession相关的创建
1.SqlSession
SqlSession主要的作用就是执行JDBC的CURD操作。
public interface SqlSession {
<T> List<T> query(String statementId, Object... params) throws Exception;
<T> T get(String statementId, Object... params) throws Exception;
int update(String statementId, Object... params) throws Exception;
int delete(String statementId, Object... params) throws Exception;
int insert(String statementId, Object... params) throws Exception;
/**
* 为了减少SqlSession的statementId硬编码和代码重复的问题。
* <p>
* 使用动态代理方法,使用JDK的动态代理,来映射执行对应的query,selectOne,update等接口
*
* @param clazz
* @param <T>
* @return
*/
<T> T getMapper(Class<?> clazz);
}
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <T> List<T> query(String statementId, Object... params) throws Exception {
DefaultExecutor defaultExecutor = new DefaultExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
return defaultExecutor.query(configuration, mappedStatement, params);
}
@Override
public <T> T get(String statementId, Object... params) throws Exception {
//查询列表接口,获取第一个值返回
List<Object> objectList = query(statementId, params);
if (objectList.size() == 1) {
return (T) objectList.get(0);
}
if (objectList.size() > 1) {
throw new RuntimeException("Multiple Return Objects Error");
}
return null;
}
@Override
public int update(String statementId, Object... params) throws Exception {
DefaultExecutor defaultExecutor = new DefaultExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
return defaultExecutor.update(configuration, mappedStatement, params);
}
@Override
public int delete(String statementId, Object... params) throws Exception {
return update(statementId, params);
}
@Override
public int insert(String statementId, Object... params) throws Exception {
return update(statementId, params);
}
@Override
public <T> T getMapper(Class<?> clazz) {
//使用JDK的动态代理技术
Object handler = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{clazz}, (proxy, method, args) -> {
// 底层还是执行JDBC
// 调用接口需要 statementId, params,
// statementId的语法为 namespace.id。
// 所以mapper.xml的命名方式约定为:namespace为Dao的全路径类名,id为执行dao的函数名称
//id
String id = method.getName();
//类名
String namespace = method.getDeclaringClass().getName();
String statementId = namespace + "." + id;
Class<?> returnType = method.getReturnType();
//如果参数个数大于1,则转换成map对象,并作为第一个参数的值传递
int parameterCount = method.getParameterCount();
if (parameterCount > 1) {
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Map<String, Object> paramsMap = new HashMap<>();
int i = 0;
for (Annotation[] parameterAnnotation : parameterAnnotations) {
for (Annotation annotation : parameterAnnotation) {
if (annotation instanceof Param) {
Param param = (Param) annotation;
String value = param.value();
paramsMap.put(value, args[i]);
i++;
}
}
}
args[0] = paramsMap;
}
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
switch (sqlCommandType) {
case SELECT:
boolean returnMany = returnMany(returnType);
if (returnMany) {
//集合查询
return query(statementId, args);
}
//单个查询
return get(statementId, args);
case INSERT:
return insert(statementId, args);
case UPDATE:
return update(statementId, args);
case DELETE:
return delete(statementId, args);
default:
throw new RuntimeException("Sql command type error");
}
});
return (T) handler;
}
/**
* 判断返回类型时是否数集合或数组
*
* @param returnType
* @return
*/
private boolean returnMany(Class<?> returnType) {
boolean isCollection = Collection.class.isAssignableFrom(returnType);
boolean isArray = returnType.isArray();
return isCollection || isArray;
}
}
-
涉及到的Param注解类
用于指定入参的参数名称。多参数的方法需要指定名称,不然获取不到mapper.xml中像#{username}的KEY对应关系
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) public @interface Param { String value(); }栗子:
public int select(@Param("username") String username,@Param("status") Integer status)
2.创建SqlSessionFactory
工厂模式类,用于生产SqlSession
public interface SqlSessionFactory {
SqlSession openSession();
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
DefaultSqlSession sqlSession = new DefaultSqlSession(configuration);
return sqlSession;
}
}
3.SqlSessionFactoryBuilder
public class SqlSessionFactoryBuilder {
/**
* 通过配置输入流,创建SqlSessionFactory
*
* @author modongning
* @date 24/09/2020 10:51 PM
*/
public static SqlSessionFactory build(InputStream inputStream) throws ParserException {
/*
1.使用dom4j解析配置文件,保存到配置容器Configuration中
*/
Parser configurationParser = new XmlConfigurationParser();
Configuration configuration = (Configuration) configurationParser.parse(inputStream);
/*
创建SqlSessionFactory,用来生产SqlSession
实现DefaultSqlSessionFactory类。由于后期一直都需要使用configuration的配置信息,所有使用带参构造函数传入
*/
SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return sqlSessionFactory;
}
}
5.创建sql执行器:Executor
1.具体实现
/**
* SQL执行器
* 核心作用是解析sql,处理sql参数,sql执行,返回sql执行结果
*/
public interface Executor {
/**
* 统计查询操作
*
* @param configuration
* @param mappedStatement
* @param params
* @return
* @throws Exception
*/
int count(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
/**
* 插入操作
*
* @param configuration
* @param mappedStatement
* @param params
* @return
*/
int insert(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
/**
* 删除操作
*
* @param configuration
* @param mappedStatement
* @param params
* @return
* @throws Exception
*/
int delete(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
/**
* 更新操作
*
* @param configuration
* @param mappedStatement
* @param params
* @return
* @throws Exception
*/
int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
/**
* 执行查询操作
*
* @param configuration 核心配置容器
* @param mappedStatement sql配置映射
* @param params 参数
* @param <T>
* @return
*/
<T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
}
/**
* 默认实现的sql执行器
*/
public class DefaultExecutor implements Executor {
@Override
public int count(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
return update(configuration, mappedStatement, params);
}
@Override
public int insert(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
return update(configuration, mappedStatement, params);
}
@Override
public int delete(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
return update(configuration, mappedStatement, params);
}
@Override
public int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
//TODO 插入,删除,统计都是返回的是数值类型的结果,所有统一都使用update操作执行
return (int) executor(configuration, mappedStatement, params);
}
@Override
public <T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
ResultSet resultSet = (ResultSet) executor(configuration, mappedStatement, params);
//6.封装返回值
String resultType = mappedStatement.getResultType();
Class<?> resultTypeClass = getClassBy(resultType);
List<Object> resultList = new ArrayList<>();
while (resultSet.next()) {
//数据对象
Object o = resultTypeClass.newInstance();
//元数据,对象包含了数据的字段名
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
//字段名
String columnName = metaData.getColumnName(i);
//字段值
Object value = resultSet.getObject(columnName);
//有了对象字段名和值之后,使用反射或者内省,设置对象对应的值
//获取resultTypeClass类的columnName的属性描述
//TODO 类似select count(*) 的统计语句是返回ResultSet的,需要根据resultType返回类型判断是不是对象,是对象的话再通过反射赋值。
try {
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
//获取写方法
Method writeMethod = propertyDescriptor.getWriteMethod();
//通过字段的写方法,设置对象字段的值
writeMethod.invoke(o, value);
} catch (IntrospectionException e) {
//没有对应的字段,则直接忽略
continue;
}
}
resultList.add(o);
}
return (List<T>) resultList;
}
private Object executor(Configuration configuration, MappedStatement mappedStatement, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
//1. 获取数据库连接
Connection connection = configuration.getDataSource().getConnection();
/*
2.获取sql配置
SELECT * FROM user WHERE id = #{id};
*/
String mappedStatementSql = mappedStatement.getSql();
//3.解析sql配置:SELECT * FROM user WHERE id = ?
BoundSql boundSql = getBoundSql(mappedStatementSql);
//4.设置参数
//预处理对象
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
String parameterType = mappedStatement.getParameterType();
//获取参数类的类型
Class<?> parameterTypeClass = getClassBy(parameterType);
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
for (int i = 0; i < parameterMappingList.size(); i++) {
//TODO 这里后期加入更多参数判断逻辑
Object param = params[0];
if (null == parameterTypeClass
|| (parameterTypeClass.isAssignableFrom(Integer.class)
|| parameterTypeClass.isAssignableFrom(Long.class)
|| parameterTypeClass.isAssignableFrom(Short.class)
|| parameterTypeClass.isAssignableFrom(Number.class)
|| parameterTypeClass.isAssignableFrom(Double.class)
|| parameterTypeClass.isAssignableFrom(Float.class)
|| parameterTypeClass.isAssignableFrom(Character.class)
|| parameterTypeClass.isAssignableFrom(Boolean.class)
|| parameterTypeClass.isAssignableFrom(Byte.class))
) {
//TODO 这里待改进,后续可以通过判断类型,根据参数名称,存入Map中,参数名=value的形式,统一当成对象处理
preparedStatement.setObject(i + 1, param);
continue;
}
ParameterMapping parameterMapping = parameterMappingList.get(i);
String content = parameterMapping.getContent();
Object value = null;
if (param instanceof Map) {
value = ((Map) param).get(content);
} else {
//通过反射获取到参数中content名称的字段
Field contentField = parameterTypeClass.getDeclaredField(content);
contentField.setAccessible(true);
//从入参的对象中获取参数值
//从参数0的对象中,获取对象content字段的值
value = contentField.get(param);
}
preparedStatement.setObject(i + 1, value);
}
//5.执行sql
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
if (SqlCommandType.SELECT.equals(sqlCommandType)) {
return preparedStatement.executeQuery();
}
return preparedStatement.executeUpdate();
}
private Class<?> getClassBy(String parameterType) throws ClassNotFoundException {
if (null != parameterType) {
return Class.forName(parameterType);
}
return null;
}
/**
* 对#{}解析,
* 1.将#{}使用?进行替换
* 2.解析出的#{}的值进行存储,后续需要对这个值通过反射获取参数值
*
* @param mappedStatementSql
* @return
*/
private BoundSql getBoundSql(String mappedStatementSql) {
//标记处理类,配置标记解析器来完成对占位符的解析处理工作。
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
//解析后的sql: SELECT * FROM user WHERE id = ?
String sql = genericTokenParser.parse(mappedStatementSql);
//#{}解析出来的参数名称
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
return new BoundSql(sql, parameterMappings);
}
}
3. Executor 中需要使用到的相关类
1.BoundSql
-
/** * Sql配置映射类的配置sql解析后的预处理sql和参数名称容器 */ public class BoundSql { /** * sql配置解析后的预处理sql */ private String sqlText; /** * sql配置解析后参数名称列表 */ private List<ParameterMapping> parameterMappingList; public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) { this.sqlText = sqlText; this.parameterMappingList = parameterMappingList; } }
2.ParameterMapping
存储#{}解析出来的参数名称
public class ParameterMapping {
public ParameterMapping(String content) {
this.content = content;
}
/**
* 参数名称
*/
private String content;
}
3.ParameterMappingTokenHandler
存放sql语句占位符#{}解析出来的字段名称:如#{username} 得到 "username"
public interface TokenHandler {
String handleToken(String content);
}
/**
* 存放的是sql语句占位符#{}之间的值,并返回替换符号"?"
* 如:#{useranme}
* 则 ParameterMapping.content 的值就是 "useranme"
*/
public class ParameterMappingTokenHandler implements TokenHandler {
private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
// context是参数名称 #{id} #{username}
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
private ParameterMapping buildParameterMapping(String content) {
ParameterMapping parameterMapping = new ParameterMapping(content);
return parameterMapping;
}
public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}
public void setParameterMappings(List<ParameterMapping> parameterMappings) {
this.parameterMappings = parameterMappings;
}
}
4.GenericTokenParser
通用符号解析器,用于解析#{}的占位符
public class GenericTokenParser {
private final String openToken; //开始标记
private final String closeToken; //结束标记
private final TokenHandler handler; //标记处理器
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
/**
* 解析${}和#{}
* @param text
* @return
* 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。
* 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现
*/
public String parse(String text) {
// 验证参数问题,如果是null,就返回空字符串。
if (text == null || text.isEmpty()) {
return "";
}
// 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。
int start = text.indexOf(openToken, 0);
if (start == -1) {
return text;
}
// 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder,
// text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while (start > -1) {
// 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理
if (start > 0 && src[start - 1] == '\\') {
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
//重置expression变量,避免空指针或者老数据干扰。
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {////存在结束标记时
if (end > offset && src[end - 1] == '\\') {//如果结束标记前面有转义字符时
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {//不存在转义字符,即需要作为参数进行处理
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
//首先根据参数的key(即expression)进行参数处理,返回?作为占位符
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}