一、JDBC回顾及问题分析
CREATE TABLE `user` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT,`username` varchar(50) DEFAULT NULL,`password` varchar(50) DEFAULT NULL,`birthday` varchar(50) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*** jdbc测试类* @author lipengyu*/public class JdbcTest {public static void main(String[] args) {Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;try {// 加载数据库驱动Class.forName("com.mysql.jdbc.Driver");// 通过驱动管理类获取数据库链接connection = DriverManager.getConnection("jdbc:mysql:///mybatis1?characterEncoding=utf-8", "root", "");// 定义sql语句 ?表示占位符String sql = "select * from user where username = ?";// 获取预处理statementpreparedStatement = connection.prepareStatement(sql);// 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值preparedStatement.setString(1, "lucy");// 向数据库发出sql执行查询,查询出结果集resultSet = preparedStatement.executeQuery();// 遍历查询结果集while (resultSet.next()) {long id = resultSet.getLong("id");String username = resultSet.getString("username");//封装UserUser user = new User();user.setId(id);user.setUsername(username);System.out.println(user);}} catch (Exception e) {e.printStackTrace();} finally {// 释放资源if (resultSet != null) {try {resultSet.close();} catch (SQLException e) {e.printStackTrace();}}if (preparedStatement != null) {try {preparedStatement.close();} catch (SQLException e) {e.printStackTrace();}}if (connection != null) {try {connection.close();} catch (SQLException e) {e.printStackTrace();}}}}}
- 数据库配置信息存在硬编码问题
解决:配置文件 - 频繁创建释放数据库连接
解决:连接池 - sql语句、设置参数、获取结果集均存在硬编码问题
解决:配置文件
(虽然数据库配置信息和sql配置信息都可以写入配置文件,但是因为数据库配置信息不常变化,而sql配置信息经常变化,所以建议配置文件分开) - 手动封装返回结果集,较为繁琐
解决:反射、内省(https://blog.csdn.net/z714405489/article/details/84650307)
反射是在运行状态把Java类中的各种成分映射成相应的Java类,可以动态的获取所有的属性以及动态调用任意一个方法,强调的是运行状态。
内省(IntroSpector)是Java 语言针对 Bean 类属性、事件的一种缺省处理方法。主要使用方式就是通过类 Introspector 的 getBeanInfo方法 来获取某个对象的 BeanInfo 信息,然后通过 BeanInfo 来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的 getter/setter 方法,然后我们就可以通过反射机制来调用这些方法,这就是内省机制。
二、自定义持久层框架思路分析
- 使用端:(项目):引入自定义持久层框架的jar包
提供两部分配置信息:数据库配置信息、sql配置信息、参数类型、返回值类型。
使用配置文件来提供这部分配置信息;- (1)sqlMapConfig.xml:存放数据库配置信息,存放mapper.xml的全路径
- (2)mapper.xml:存放sql配置信息
- 自定义持久层框架本身:(工程):本质就是对JDBC代码进行了封装
- (1)加载配置文件:根据配置文件的路径,加载配置文件成字节输入流,存储在内存中。
创建Resources类 方法:InputStream getResourceAs Stream(String path) - (2)创建两个javaBean:(容器对象):存放的就是对配置文件解析出来的内容
Configuration: 核心配置类:村饭sqlMapperConfig.xml解析出来的内容
MappedStatement: 映射配置类:存放mapper.xml解析出来的内容 - (3)解析配置文件:dom4j
创建类:SqlSessionFactoryBuilder 方法 build(InputStream in)- 第一:使用dom4j解析配置文件,将解析出来的内容封装到容器对象中
- 第二:创建SqlSessionFactory对象;生产sqlSession; 会话对象(工厂模式)
- (4)创建SqlSessionFactory接口及实现类DefaultSqlSessionFactory
- 第一:openSession(): 生产sqlSession
- (5)创建SqlSession接口及实现类DefaultSession
定义对数据库的crud操作:
selectList()
selectOne()
update()
delete() - (6)创建Executor接口及实现类SimpleExecutor实现类
query(Configuration, MappedStatement, Object… params); 执行的就是JDBC三、IPersistence_Test测试类编写
- (1)加载配置文件:根据配置文件的路径,加载配置文件成字节输入流,存储在内存中。
新建项目IPersistence_test
在resource下新建文件sqlMapConfig.xml
<configuration><datasource><property name="driverClass" value="com.mysql.jdbc.Driver"/><property name="user" value="root"/><property name="password" value=""/><property name="jdbcUrl" value="jdbc:mysql:///mybatis1"/></datasource><!-- 存放mapper.xml的全路径--><mapper resource="mapper.xml"><mapper resource="UserMapper.xml"/></configuration>
在resource下新建文件UserMapper.xml
<mapper namespace="user"><!--sql的唯一表示:namespace.id来组成: statementId--><select id="selectList" resultType="com.lpy.model.User">select * from user</select><!--User user = new User;user.setId(1);user.setUsername("zhangsan")--><select id="selectOne" parameterType="com.lpy.model.User" resultType="com.lpy.model.User">select * from user where id = #{id} and username = #{username}</select></mapper>
创建User实体类
/*** User实体类* @author lipengyu*/@Getter@Setter@ToStringpublic class User {private Long id;private String username;}
四、IPersistence自定义持久层框架编写
新建模块IPersistence
首先为了加载配置文件,在com.lpy.io下新建Resources,将文件读取成字节输入流
/*** 获取资源类* @author lipengyu*/public class Resources {/*** 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中*/public static InputStream getResourceAsStream(String path) {return Resources.class.getClassLoader().getResourceAsStream(path);}}
修改IPersistence的pom.xml文件
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.lpy</groupId><artifactId>IPersistence</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.encoding>UTF-8</maven.compiler.encoding><java.version>1.8</java.version><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><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><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.10</version></dependency><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><dependency><groupId>jaxen</groupId><artifactId>jaxen</artifactId><version>1.1.6</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.14</version></dependency></dependencies></project>
将IPersistence打包后引入IPersistence_test
修改IPersistence_test 的pom.xml文件
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.lpy</groupId><artifactId>IPersistence_test</artifactId><version>1.0-SNAPSHOT</version><!--引入自定义持久层框架的依赖--><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.14</version></dependency><dependency><groupId>org.lpy</groupId><artifactId>IPersistence</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies></project>
在模块IPersistence_test编写测试类com.lpy.test.IPersistenceTest,每完成一步都可以自测一下
public class IPesistenceTest {public void test(){InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");}}
为了将配置文件解析成对象,我们先创建两个javabean
在IPersistence com.lpy.pojo中新建MappedStatement,主要存放单个sql的信息
/*** 将mapper.xml中sql标签解析成对象* @author lipengyu*/@Getter@Setterpublic class MappedStatement {/*** id标识*/private String id;/*** 返回值类型*/private Class<?> parameterType;/*** 参数值类型*/private Class<?> resultType;/*** sql语句*/private String sql;public MappedStatement(String id, Class<?> parameterType, Class<?> resultType, String sql) {this.id = id;this.parameterType = parameterType;this.resultType = resultType;this.sql = sql;}}
com.lpy.pojo中新建Configuration,主要存放数据源信息和所有的sql信息
/*** 将sqlMapConfig.xml解析成对象* @author lipengyu*/@Getter@Setterpublic class Configuration {/*** 数据源*/private DataSource dataSource;/*** key: statementId value: 封装好的mappedStatement对象*/private Map<String, MappedStatement> mappedStatementMap = new HashMap<>();}
com.lpy.sqlSession中新建类SqlSessionFactoryBuilder
开始解析配置文件 (可以暂时忽略第二)
/*** 解析配置文件以及生产sqlSession* @author lipengyu*/public class SqlSessionFactoryBuilder {private Configuration configuration;public SqlSessionFactoryBuilder() {this.configuration = new Configuration();}public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {// 第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中XMLConfigerBuilder builder = new XMLConfigerBuilder(configuration);Configuration configuration = builder.parseConfig(in);// 第二:创建sqlSessionFactory对象 工厂类:生产sqlSession:会话对象return new DefaultSqlSessionFactory(configuration);}}
com.lpy.config中新建类XMLConfigerBuilder
首先解析sqlMapConfig.xml
/*** 解析sqlMapConfig.xml具体实现、解析mapper.xml* @author lipengyu*/public class XMLConfigerBuilder {private Configuration configuration;public XMLConfigerBuilder(Configuration configuration) {this.configuration = configuration;}/*** 该方法就是使用dom4j对配置文件解析,封装Configuration** 从当前节点的儿子节点中选择名称为 item 的节点。* SelectNodes("item")* 从根节点的儿子节点中选择名称为 item 的节点。* SelectNodes("/item")* 从任意位置的节点上选择名称为 item 的节点。要重点突出这个任意位置,它不受当前节点的影响,也就是说假如当前节点是在第 100 层(有点夸张),也可以选择第一层的名称为 item 的节点。* SelectNodes("//item")*/public Configuration parseConfig(InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {Document read = new SAXReader().read(in);// <configuration>Element rootElement = read.getRootElement();List<Element> propertyElements = rootElement.selectNodes("//property");Properties properties = new Properties();propertyElements.forEach(element -> {properties.put(element.attributeValue("name"), element.attributeValue("value"));});// 连接池ComboPooledDataSource dataSource = new ComboPooledDataSource();dataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));dataSource.setDriverClass(properties.getProperty("driverClass"));dataSource.setUser(properties.getProperty("user"));dataSource.setPassword(properties.getProperty("password"));configuration.setDataSource(dataSource);// mapper.xml解析:拿到路径->字节输入流->dom4j进行解析List<Element> mapperElements = rootElement.selectNodes("//mapper");XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);for (Element mapperElement : mapperElements) {String mapperPath = mapperElement.attributeValue("resource");InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);xmlMapperBuilder.parse(resourceAsStream);}return configuration;}}
com.lpy.config中新建类XMLMapperBuilder
解析mapper.xml
/*** 解析mapper.xml具体实现* @author lipengyu*/public class XMLMapperBuilder {public Configuration configuration;public XMLMapperBuilder(Configuration configuration) {this.configuration = configuration;}/*** 解析mapper.xml*/public void parse(InputStream resourceAsStream) throws DocumentException, ClassNotFoundException {Document read = new SAXReader().read(resourceAsStream);Element rootElement = read.getRootElement();String namespace = rootElement.attributeValue("namespace");List<Element> selectElements = rootElement.selectNodes("select");Map<String, MappedStatement> mappedStatementMap = configuration.getMappedStatementMap();for (Element selectElement : selectElements) {String id = selectElement.attributeValue("id");String parameterType = selectElement.attributeValue("parameterType");String resultType = selectElement.attributeValue("resultType");String sql = selectElement.getTextTrim();// 获取参数类型Class<?> parameterTypeClass = getClassType(parameterType);// 获取返回结果类型Class<?> reusltClassType = getClassType(resultType);mappedStatementMap.put(namespace + "." + id, new MappedStatement(id, parameterTypeClass, reusltClassType, sql));}}private Class<?> getClassType(String classType) throws ClassNotFoundException {if (classType != null) {return Class.forName(classType);}return null;}}
解析完成将配置信息封装到Configuration中,接下来使用工厂生成sqlSession
在com.lpy.sqlSession下创建接口SqlSessionFactory
/*** SqlSession工厂* @author lipengyu*/public interface SqlSessionFactory {SqlSession openSession();}
以及实现类DefaultSqlSessionFactory
/*** SqlSession工厂 默认实现* @author lipengyu*/public class DefaultSqlSessionFactory implements SqlSessionFactory {private Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}@Overridepublic DefaultSqlSession openSession() {return new DefaultSqlSession(configuration);}}
在com.lpy.sqlSession下创建接口SqlSession
/*** SqlSession* @author lipengyu*/public interface SqlSession {/*** 查询所有*/<E> List<E> selectList(String statementId, Object ...param) throws Exception;/*** 查询单个*/<T> T selectOne(String statementId, Object ...param) throws Exception;}
以及实现类DefaultSqlSession
/*** SqlSession默认实现* @author lipengyu*/public class DefaultSqlSession implements SqlSession {private Configuration configuration;public DefaultSqlSession(Configuration configuration) {this.configuration = configuration;}public Executor simpleExecutor = new SimpleExecutor();@Overridepublic <E> List<E> selectList(String statementId, Object... param) throws Exception {MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);return simpleExecutor.query(configuration, mappedStatement, param);}@Overridepublic <T> T selectOne(String statementId, Object... param) throws Exception {List<Object> objects = selectList(statementId, param);if (objects.size() > 1) {throw new RuntimeException("返回结果过多");}return (T) objects.get(0);}}
这一步我们通过openSession方法创建了sqlSession以便我们操作数据库,简单封装了基本查询操作,还涉及了Executor,Executor接口就是对jdbc的实现。
在com.lpy.sqlSession下创建接口Executor
/*** 执行器接口* @author lipengyu*/public interface Executor {<E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object[] param) throws Exception;}
以及实现SimpleExecutor
public class SimpleExecutor implements Executor {/*** 查询*/@Overridepublic <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object[] param) throws SQLException, NoSuchFieldException, IllegalAccessException, IntrospectionException, InstantiationException, InvocationTargetException {// 1. 注册驱动 获取连接Connection connection = configuration.getDataSource().getConnection();// 2. 获取sql语句:select * from user where id = #{id} and username = #{username}// 转换sql语句:select * from user where id = ? and username = ? , 转换的过程中,还需要对#{}里面的值进行解析存储String sql = mappedStatement.getSql();BoundSql boundSql = getBoundSql(sql);// 3. 获取预处理对象 preparedStatementPreparedStatement preparedStatement = connection.prepareStatement(boundSql.getBoundSql());// 4. 设置参数// 获取参数类型Class<?> parameterType = mappedStatement.getParameterType();List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();for (int i = 0; i < parameterMappingList.size(); i++) {ParameterMapping parameterMapping = parameterMappingList.get(i);// 获取当前参数的名字String content = parameterMapping.getContent();// 通过反射找到该属性Field declaredField = parameterType.getDeclaredField(content);// 暴力访问declaredField.setAccessible(true);// param[0] 指对象,从该对象取出该属性的值Object o = declaredField.get(param[0]);// 设置参数preparedStatement.setObject(i + 1, o);}// 5. 执行sqlResultSet resultSet = preparedStatement.executeQuery();// 6. 封装返回结果集// 获取元数据ResultSetMetaData metaData = resultSet.getMetaData();// 获取列的数量int columnCount = metaData.getColumnCount();// 获取返回值类型Class<?> resultType = mappedStatement.getResultType();List<E> results = new ArrayList<>();while (resultSet.next()) {E e = (E) resultType.newInstance();for (int i = 1; i < columnCount + 1; i++) {String columnName = metaData.getColumnName(i);// 使用反射或者内省,根据数据库和实体相关的对应关系,完成封装。PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultType);Method writeMethod = propertyDescriptor.getWriteMethod();// 执行写方法writeMethod.invoke(e, resultSet.getObject(columnName));}results.add(e);}return results;}/*** 完成对#{}的解析工作:* 1. 将#{}使用?进行代替。* 2. 解析出#{}里面的值进行存储*/private BoundSql getBoundSql(String sql) {// 标记处理类:配置标记解析器来完成对占位符的解析处理工作ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler();GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);// 解析出来的sqlString parse = parser.parse(sql);// #{}里面解析出来的参数名称return new BoundSql(parse, handler.getParameterMappings());}}
在SimpleExecutor 中第二步需要转换sql,需要工具类GenericTokenParser、ParameterMapping、ParameterMappingTokenHandler、TokenHandler
在com.lpy.utils下面新建
/*** @author Clinton Begin*/public interface TokenHandler {String handleToken(String content);}
/*** @author Clinton Begin*/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();}}
@Getter@Setterpublic class ParameterMapping {private String content;public ParameterMapping(String content) {this.content = content;}}
/*** @author Clinton Begin*/public class ParameterMappingTokenHandler implements TokenHandler {private List<ParameterMapping> parameterMappings = new ArrayList<>();// content是参数名称 #{id} #{username}@Overridepublic 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;}}
以及转换后封装的boundSql
在com.lpy.config 下面新建
/*** sql及参数映射* @author lipengyu*/@Getter@Setterpublic class BoundSql {/*** 解析过后的sql*/private String boundSql;/*** 参数映射*/private List<ParameterMapping> parameterMappingList;public BoundSql(String boundSql, List<ParameterMapping> parameterMappingList) {this.boundSql = boundSql;this.parameterMappingList = parameterMappingList;}}
五、Client端运行测试
/*** 测试类* @author lipengyu*/public class IPesistenceTest {public static void main(String[] args) throws Exception {SqlSessionFactory build = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("sqlMapConfig.xml"));SqlSession sqlSession = build.openSession();List<User> users = sqlSession.selectList("user.selectList");System.out.println(users);User param = new User();param.setId(1L);param.setUsername("lucy");User user = sqlSession.selectOne("user.selectOne", param);System.out.println(user);}}
此处我遇到了个问题,我把User的id属性设置成了Integer类型,结果报错 argument type mismatch,我看了一下我的数据库类型明明是int,去翻mysql的文档,发现unsigned int对应的是long。
修改后就没问题啦~
六、功能扩展-getMapper方法实现
测试通过后,我们想通常调用查询方法都是通过持久层结果的方式调用,所以我们在IPersistence_test 中 com.lpy.dao 创建IUserDao
/*** @author lipengyu*/public interface IUserDao {List<User> findAll() throws Exception;User findByCondition(User user) throws Exception;}
在 com.lpy.dao.impl中创建IUserDaoImpl
/*** @author lipengyu*/public class IUserDaoImpl implements IUserDao {@Overridepublic List<User> findAll() throws Exception {SqlSessionFactory build = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("sqlMapConfig.xml"));SqlSession sqlSession = build.openSession();return sqlSession.selectList("user.selectList");}@Overridepublic User findByCondition(User param) throws Exception {SqlSessionFactory build = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("sqlMapConfig.xml"));SqlSession sqlSession = build.openSession();return sqlSession.selectOne("user.selectOne", param);}}
通过IUserDao调用查询方法
IUserDao dao = new IUserDaoImpl();System.out.println(dao.findAll());System.out.println(dao.findByCondition(param));
虽然可以调用成功,但有一些问题我们需要分析一下
自定义持久层框架问题分析:
- Dao层使用自定义持久层框架,存在代码重复,整个操作的过程模板重复(加载配置文件、创建sqlSessionFactory、生产sqlSession)
- statementId存在硬编码问题
解决思路:使用代理模式生成Dao层接口的代理实现类
在SqlSession中新增方法getMapper
<T> T getMapper(Class<?> mapperClass);
在DefaultSqlSession中新增方法实现
@Override@SuppressWarnings("unchecked")public <T> T getMapper(Class<?> mapperClass) {return (T)Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, (proxy, method, args) -> {String methodName = method.getName();String className = method.getDeclaringClass().getName();// 拼接keyString key = className + "." + methodName;// 判断是否有泛型Type genericReturnType = method.getGenericReturnType();if (genericReturnType instanceof ParameterizedType) {return selectList(key, args);}return selectOne(key, args);});}
此处有两个需要注意的点
- 如果想使用getMapper方法,需要规范xxxMapper.xml,namespace必须是className,也就是全类名,这样整个statementId,可以通过代理的方式直接调用查询sql,不用额外写DAO层的实现了
- 上方判断是否有泛型是取巧的写法,因为查询多个会返回List
,单个会返回实体,所以有泛型的就调用selectList,没有的就selectOne。(只是为了实现简单的功能)
之后我们可以注释掉之前写的IUserDaoImpl了,并且要修改一下UserMapper.xml,不过为了保留过程,我新增了NewUserMapper.xml
<mapper namespace="com.lpy.dao.IUserDao"><select id="findAll" resultType="com.lpy.model.User">select * from user</select><select id="findByCondition" parameterType="com.lpy.model.User" resultType="com.lpy.model.User">select * from user where id = #{id} and username = #{username}</select></mapper>
不要忘记在sqlMapConfig.xml中加入配置,引用新的配置文件
<mapper resource = "NewUserMapper.xml"/>
测试一下也是没问题的
IUserDao userDao = sqlSession.getMapper(IUserDao.class);List<User> all = userDao.findAll();all.forEach(System.out::println);User byCondition = userDao.findByCondition(param);System.out.println(byCondition);

