为何要手写自定义持久层框架?
- JDBC 编码的弊端
- 会造成硬编码问题(无法灵活切换数据库驱动) 频繁创建和释放数据库连接造成系统资源浪费 影响系统性能
- sql 语句存在硬编码,造成代码不易维护,实际应用中 sql 变化可能较大,变动 sql 需要改 Java 代码
- 使用 preparedStatement 向占有位符号传参数存在硬编码, 因 sql 语句的 where 条件不确定甚至没有where条件,修改 sql 还要修改代码 系统不易维护
- 对结果集解析也存在硬编码, sql变化导致解析代码变化
- 更有助于读 mybatis 持久层框架源码
JDBC代码
代码语言:javascript复制public class jdbcConnection {
private static Connection connection = null;
private static PreparedStatement preparedStatement = null;
private static ResultSet resultSet = null;
public static void main(String[] args) {
try {
// 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 通过驱动管理类获取数据库连接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/huodd", "root", "1234");
// 定义sql语句 ? 表示占位符
String sql = "select id,username from user where id = ?";
// 获取预处理对象 statement
PreparedStatement preparedStatement = (PreparedStatement) connection.prepareStatement(sql);
// 设置参数 第一个参数为 sql 语句中参数的序号(从1开始) 第二个参数为 设置的参数值
preparedStatement.setInt(1, 1);
// 向数据库发出sql执行查询 查询出结果集
resultSet = preparedStatement.executeQuery();
// 遍历查询结果集
while (resultSet.next()) {
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
// 封装对象
User user = new User();
user.setId(id);
user.setUsername(username);
System.out.println(user);
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
// 释放资源
if (resultSet != null) {
resultSet.close();
}
if (preparedStatement != null) {
preparedStatement.close();
}
if (connection != null) {
connection.close();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
解决问题的思路
- 数据库频繁创建连接、释放资源 -> 连接池
- sql语句及参数硬编码 -> 配置文件
- 手动解析封装结果集 -> 反射、内省
编码前思路整理
- 创建、读取配置文件
- sqlMapConfig.xml 存放数据库配置信息
- userMapper.xml :存放sql配置信息
- 根据配置文件的路径,加载配置文件成字节输入流,存储在内存中Resources#getResourceAsStream(String path)
- 创建两个JavaBean存储配置文件解析出来的内容
- Configuration :核心配置类 ,存放 sqlMapConfig.xml解析出来的内容
- MappedStatement:映射配置类:存放mapper.xml解析出来的内容
- 解析配置文件(使用dom4j)
- 创建类:SqlSessionFactoryBuilder#build(InputStream in) -> 设计模式之构建者模式
- 使用dom4j解析配置文件,将解析出来的内容封装到容器对象(JavaBean)中
- 创建 SqlSessionFactory 接口及实现类DefaultSqlSessionFactory
- SqlSessionFactory对象,生产sqlSession会话对象 -> 设计模式之工厂模式
- 创建 SqlSession接口及实现类DefaultSqlSession
- 定义对数据库的CRUD操作
- selectList()
- selectOne()
- update()
- delete()
- 创建Executor接口及实现类SimpleExecutor实现类
- query(Configuration configuration, MappedStatement mapStatement, Object... orgs) 执行的就是JDBC代码
- 测试代码
用到的设计模式
- 构建者模式
- 工厂模式
- 代理模式
进入编码
1.创建、读取配置文件
sqlMapConfig.xml 存放数据库配置信息
代码语言:javascript复制<configuration>
<dataSource>
<!-- 引入数据库连接信息 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///huodd"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</dataSource>
<!-- 引入sql配置文件 -->
<mapper resource="userMapper.xml"></mapper>
</configuration>
userMapper.xml 存放sql配置信息
代码语言:javascript复制<mapper namespace="user">
<!-- sql 的唯一标识: namespace.id 组成 => statementId 如 当前的为 user.selectList -->
<select id="selectList" resultType="com.huodd.pojo.User" paramterType="com.huodd.pojo.User">
select * from user
</select>
<select id="selectOne" paramterType="com.huodd.pojo.User" resultType="com.huodd.pojo.User">
select * from user where id = #{id} and username =#{username}
</select>
</mapper>
User.java
代码语言:javascript复制public class User {
private Integer id;
private String username;
... 省略getter setter 方法
... 省略 toString 方法
}
pom.xml 中引入依赖
代码语言:javascript复制<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>
创建两个JavaBean对象 用于存储解析的配置文件的内容(Configuration.java、MappedStatement.java)
代码语言:javascript复制public class Configuration {
// 数据源
private DataSource dataSource;
//map集合 key:statementId value:MappedStatement
private Map<String,MappedStatement> mappedStatementMap = new HashMap<>();
... 省略getter setter 方法
}
代码语言:javascript复制public class MappedStatement {
// id
private String id;
// sql 语句
private String sql;
// 参数值类型
private Class<?> paramterType;
// 返回值类型
private Class<?> resultType;
... 省略getter setter 方法
}
创建Resources工具类 并编写静态方法getResourceAsSteam(String path)
代码语言:javascript复制public class Resources {
/**
* 根据配置文件的路径 将配置文件加载成字节输入流 存储在内存中
* @param path
* @return InputStream
*/
public static InputStream getResourceAsStream(String path) {
InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
return resourceAsStream;
}
}
2.解析配置文件(使用dom4j)
创建 SqlSessionFactoryBuilder类 并添加 build 方法
代码语言:javascript复制public class SqlSessionFactoryBuilder {
public SqlSessionFactory build (InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {
// 1. 使用 dom4j 解析配置文件 将解析出来的内容封装到Configuration中
XMLConfigerBuilder xmlConfigerBuilder = new XMLConfigerBuilder();
// configuration 是已经封装好了sql信息和数据库信息的对象
Configuration configuration = xmlConfigerBuilder.parseConfig(in);
// 2. 创建 SqlSessionFactory 对象 工厂类 主要是生产sqlSession会话对象
DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return defaultSqlSessionFactory;
}
}
代码语言:javascript复制public class XMLConfigerBuilder {
private Configuration configuration;
public XMLConfigerBuilder() {
this.configuration = new Configuration();
}
/**
* 该方法 使用dom4j对配置文件进行解析 封装Configuration
* @param in
* @return
*/
public Configuration parseConfig (InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {
Document document = new SAXReader().read(in);
// <configuation>
Element rootElement = document.getRootElement();
List<Element> propertyElements = rootElement.selectNodes("//property");
Properties properties = new Properties();
for (Element propertyElement : propertyElements) {
properties.setProperty(propertyElement.attributeValue("name"), propertyElement.attributeValue("value"));
}
// 连接池
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
comboPooledDataSource.setUser(properties.getProperty("user"));
comboPooledDataSource.setPassword(properties.getProperty("password"));
// 填充 configuration
configuration.setDataSource(comboPooledDataSource);
// mapper 部分 拿到路径 -> 字节输入流 -> 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;
}
}
代码语言:javascript复制public class XMLMapperBuilder {
private Configuration configuration;
public XMLMapperBuilder(Configuration configuration) {
this.configuration = configuration;
}
public void parse(InputStream inputStream) throws DocumentException, ClassNotFoundException {
Document document = new SAXReader().read(inputStream);
// <mapper>
Element rootElement = document.getRootElement();
String namespace = rootElement.attributeValue("namespace");
List<Element> select = rootElement.selectNodes("//select");
for (Element element : select) {
// 获取 id 的值
String id = element.attributeValue("id");
String paramterType = element.attributeValue("paramterType");
String resultType = element.attributeValue("resultType");
// 输入参数 class
Class<?> paramterTypeClass = getClassType(paramterType);
// 返回结果 class
Class<?> resultTypeClass = getClassType(resultType);
// sql 语句
String sqlStr = element.getTextTrim();
// 封装 mappedStatement
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setId(id);
mappedStatement.setParamterType(paramterTypeClass);
mappedStatement.setResultType(resultTypeClass);
mappedStatement.setSql(sqlStr);
// statementId
String key = namespace "." id;
// 填充 configuration
configuration.getMappedStatementMap().put(key, mappedStatement);
}
}
private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
Class<?> aClass = Class.forName(paramterType);
return aClass;
}
}
3.创建 SqlSessionFactory 接口及实现类DefaultSqlSessionFactory
代码语言:javascript复制public interface SqlSessionFactory {
SqlSession openSession();
}
代码语言:javascript复制public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession(configuration);
}
}
4. 创建 SqlSession接口及实现类DefaultSqlSession
代码语言:javascript复制public interface SqlSession {
<E> List<E> selectList(String statementId, Object... param) throws Exception;
<T> T selectOne(String statementId, Object... params) throws Exception;
void close() throws SQLException;
}
代码语言:javascript复制public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
// 处理器对象
private Executor simpleExcutor = new SimpleExecutor();
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <E> List<E> selectList(String statementId, Object... param) throws Exception {
// 完成对 simpleExcutor里的query方法的调用
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
List<E> list = simpleExcutor.query(configuration, mappedStatement, param);
return list;
}
@Override
public <T> T selectOne(String statementId, Object... params) throws Exception {
List<Object> objects = selectList(statementId, params);
if (objects.size() == 1) {
return (T) objects.get(0);
} else {
throw new RuntimeException("返回结果过多");
}
}
@Override
public void close() throws SQLException {
simpleExcutor.close();
}
}
5.创建Executor接口及实现类SimpleExecutor实现类
代码语言:javascript复制public interface Executor {
<E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... param) throws Exception;
void close() throws SQLException;
}
代码语言:javascript复制public class SimpleExecutor implements Executor {
private Connection connection = null;
@Override
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... param) throws Exception {
// 注册驱动 获取连接
connection = configuration.getDataSource().getConnection();
// select * from user where id = #{id} and username = #{username}
String sql = mappedStatement.getSql();
// 对 sql 进行处理
BoundSql boundSql = getBoundSql(sql);
// select * from where id = ? and username = ?
String finalSql = boundSql.getSqlText();
// 获取传入参数类对象
Class<?> paramterTypeClass = mappedStatement.getParamterType();
// 获取预处理 preparedStatement 对象
PreparedStatement preparedStatement = connection.prepareStatement(finalSql);
// 设置参数
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
for (int i = 0; i < parameterMappingList.size(); i ) {
ParameterMapping parameterMapping = parameterMappingList.get(i);
String name = parameterMapping.getContent();
// 反射 获取某一个属性对象
Field declaredField = paramterTypeClass.getDeclaredField(name);
// 设置暴力访问
declaredField.setAccessible(true);
// 参数传递的值
Object o = declaredField.get(param[0]);
// 给占位符赋值
preparedStatement.setObject(i 1, o);
}
// 执行sql
ResultSet resultSet = preparedStatement.executeQuery();
// 封装返回结果集
// 获取返回参数类对象
Class<?> resultTypeClass = mappedStatement.getResultType();
ArrayList<E> results = new ArrayList<>();
while (resultSet.next()) {
// 取出 resultSet的元数据
ResultSetMetaData metaData = resultSet.getMetaData();
E o = (E) resultTypeClass.newInstance();
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i ) {
// 属性名/字段名
String columnName = metaData.getColumnName(i);
// 属性值/字段值
Object value = resultSet.getObject(columnName);
// 使用反射或者内省 根据数据库表和实体的对应关系 完成封装
// 创建属性描述器 为属性生成读写方法
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
// 获取写方法
Method writeMethod = propertyDescriptor.getWriteMethod();
// 向类中写入值
writeMethod.invoke(o, value);
}
results.add(o);
}
return results;
}
/**
* 转换sql语句 完成对 #{} 的解析工作
* 1. 将 #{} 使用?进行代替
* 2. 解析出 #{} 里面的值进行存储
*
* @param sql 转换前的原sql
* @return
*/
private BoundSql getBoundSql(String sql) {
// 标记处理类: 主要是配合通用解析器 GenericTokenParser 类完成对配置文件等的解析工作 其中TokenHandler 主要完成处理
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
// GenericTokenParser: 通用的标记解析器 完成了代码片段中的占位符的解析 然后根据给定的标记处理器( TokenHandler ) 来进行表达式的处理
// 三个参数: 分别为 openToken (开始标记)、 closeToken (结束标记)、 handler (标记处理器)
GenericTokenParser genericTokenParse = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
// 解析出来的sql
String parseSql = genericTokenParse.parse(sql);
// #{} 里面解析出来的参数名称
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
return boundSql;
}
@Override
public void close() throws SQLException {
connection.close();
}
}
代码语言:javascript复制public class BoundSql {
// 解析过后的 sql 语句
private String sqlText;
// 解析出来的参数
private List<ParameterMapping> parameterMappingList = new ArrayList<>();
// 有参构造方便创建时赋值
public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {
this.sqlText = sqlText;
this.parameterMappingList = parameterMappingList;
}
... 省略getter setter 方法
}
6.测试代码
代码语言:javascript复制public class IPersistenceTest {
@Test
public void test () throws Exception {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sessionFactory.openSession();
User user = new User();
user.setId(1);
user.setUsername("bd2star");
User res = sqlSession.selectOne("user.selectOne", user);
System.out.println(res);
// 关闭资源
sqlSession.close()
}
}
运行结果如下
代码语言:javascript复制User{id=1, username='bd2star'}
测试通过 调整代码
创建 接口 Dao及实现类
代码语言:javascript复制public interface IUserDao {
// 查询所有用户
public List<User> selectList() throws Exception;
// 根据条件进行用户查询
public User selectOne(User user) throws Exception;
}
代码语言:javascript复制public class UserDaoImpl implements IUserDao {
@Override
public List<User> findAll() throws Exception {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sessionFactory.openSession();
List<User> res = sqlSession.selectList("user.selectList");
sqlSession.close();
return res;
}
@Override
public User findByCondition(User user) throws Exception {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sessionFactory.openSession();
User res = sqlSession.selectOne("user.selectOne", user);
sqlSession.close();
return res;
}
}
调整测试方法
代码语言:javascript复制public class IPersistenceTest {
@Test
public void test () throws Exception {
User user = new User();
user.setId(1);
user.setUsername("bd2star");
IUserDao userDao = new UserDaoImpl();
User res = userDao.findByCondition(user);
System.out.println(res);
}
}
运行结果如下
代码语言:javascript复制User{id=1, username='bd2star'}
测试通过
7.补充
huodd.sql
代码语言:javascript复制--新建数据库
CREATE DATABASE huodd;
--使用数据库
use huodd;
--创建表
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
-- 插入测试数据
INSERT INTO `user` VALUES (1, 'bd2star');
INSERT INTO `user` VALUES (2, 'bd3star');
用到的工具类
GenericTokenParser.java
代码语言:javascript复制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();
}
}
ParameterMapping.java
代码语言:javascript复制public class ParameterMapping {
private String content;
public ParameterMapping(String content) {
this.content = content;
}
... 省略getter setter 方法
}
ParameterMappingTokenHandler.java
代码语言:javascript复制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;
}
}
TokenHandler.java
代码语言:javascript复制public interface TokenHandler {
String handleToken(String content);
}
继续优化自定义框架
通过上述自定义框架,我们解决了JDBC操作数据库带来的一些问题,例如频繁创建释放数据库连接,硬编码,手动封装返回结果等问题
但从测试类可以发现新的问题
- dao 的实现类存在重复代码 整个操作的过程模板重复 (如创建 SqlSession 调用 SqlSession方法 关闭 SqlSession)
- dao 的实现类中存在硬编码,如调用 sqlSession 方法时 参数 statementId 的硬编码
解决方案
- 通过代码模式来创建接口的代理对象
1.添加getMapper方法
删除dao的实现类 UserDaoImpl.java 我们通过代码来实现原来由实现类执行的逻辑
在 SqlSession 中添加 getMapper 方法
代码语言:javascript复制public interface SqlSession {
<T> T getMapper(Class<?> mapperClass);
}
2. 实现类实现方法
DefaultSqlSession 类中实现 getMapper 方法
代码语言:javascript复制@Override
public <T> T getMapper(Class<?> mapperClass) {
// 使用 JDK 动态代理 来为 Dao 接口生成代理对象 并返回
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
/**
*
* @param proxy 当前代理对象的引用
* @param method 当前被调用方法的引用
* @param args 传递的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 底层都还是去执行 JDBC 代码 -> 根据不同情况 调用 selectList() 或者 selectOne()
// 准备参数 1. statmentId sql语句的唯一标识 namespace.id = 接口全限定名.方法名
// 2. params -> args
// 拿到的是方法名 findAll
String methodName = method.getName();
// 拿到该类的全限定类名 com.huodd.dao.IUserDao
String className = method.getDeclaringClass().getName();
String statmentId = className "." methodName;
// 获取被调用方法的返回值类型
Type genericReturnType = method.getGenericReturnType();
// 判断是否进行了 泛型类型参数化
if (genericReturnType instanceof ParameterizedType) {
List<Object> list = selectList(statmentId, args);
return list;
}
return selectOne(statmentId, args);
}
});
return (T) proxyInstance;
}
3.调整mapper.xml配置文件
这里要注意两点
- namespace 与 dao 接口的全限定类名保持一致
- id 与 dao 接口中定义的方法名保持一致
<mapper namespace="com.huodd.dao.IUserDao">
<!-- sql 的唯一标识: namespace.id 组成 => statementId 如 当前的为 Userselect.List -->
<select id="findAll" resultType="com.huodd.pojo.User" paramterType="com.huodd.pojo.User">
select * from user
</select>
<select id="findByCondition" paramterType="com.huodd.pojo.User" resultType="com.huodd.pojo.User">
select * from user where id = #{id} and username =#{username}
</select>
</mapper>
4. 进入测试
代码语言:javascript复制public class IPersistenceTest {
@Test
public void test () throws Exception {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sessionFactory.openSession();
User user = new User();
user.setId(1);
user.setUsername("bd2star");
// 此时返回的 userDao 就是代理对象 所以它的类型就是 Proxy
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// userDao 是代理对象 调用了接口中的 findAll() 代理对象调用接口中任意方法 都会执行 invoke()
List<User> users = userDao.findAll();
System.out.println(users);
User res = userDao.findByCondition(user);
System.out.println(res);
}
}
运行结果如下
代码语言:javascript复制[User{id=1, username='bd2star'}, User{id=2, username='bd3star'}]
User{id=1, username='bd2star'}
目录结构调整
将代码分为两个模块
- 提供端(自定义持久层框架-本质就是对JDBC代码的封装)
- 使用端 (引用持久层框架的jar )
- 包含数据库配置信息
- 包含sql配置信息
- 包含sql语句
- 参数类型
- 返回值类型
项目目录结构最终为
提供端
使用端
源码地址
https://gitee.com/bx2star/mybatis-learning.git