共计 17811 个字符,预计需要花费 45 分钟才能阅读完成。
自定长久框架 mybatis
前言
JDBC 操作数据库和 Mybatis 操作数据库, 为什么应用 Mybatis 框架, 而不去应用原生过的 JDBC 操作数据库呢?
带着这么几个问题, 咱们先来看看原生的 JDBC 操作数据步骤!
JDBC 操作数据库步骤
- 1 加载驱动
- 2 创立连贯
- 3 编译 sql 语句
- 4 设置参数
- 5 执行 sql 语句
- 6 获取后果返回集
- 7 敞开连贯
JDBC 操作数据库存在的几个痛点:
- 1 首先第一步加载驱动, 这个咱们齐全能够通过反射来解决, 更换不同的数据库驱动;
- 2 创立连贯, 每一次操作数据库都要去现成的连贯数据库, 如果操作数据库很频繁, 这种开销很耗费资源, 咱们能够采纳
线程池
,` 的思路去解决! - 3 3,4 步骤能够一起来看, 编译 sql 语句, 这个通常设置一些参数, 如果是很多参数对象, 常常改变比拟大, 在硬编码过程中, 略微操作不慎可能会代码出错, 改变老本很高, 耦合性很大!
- 4 执行 sql 没什么可说的;
- 5 获取后果返回集, 查问而言后果返回, 每个查问承受的后果集不同, 此处, 对象不同, 其余代码都是反复的;
- 6 敞开连贯
以上几个剖析过程, 简直每次操作都会面临数据库连贯, 敞开, 获取后果集 (返回类型不通过, 对象类型不同) 反复代码很高, 参数设置重大耦合, 改变频繁, 出错率高;
对此反复度高的代码, 能够通过封装利用去解决;
对于驱动, 能够通过配置文件, 更换不同的数据库驱动;
对于频繁连贯, 能够通过连接池去解决;
对于设置参数, 获取后果集, 能够通过配置文件, 以及泛型, 反射, 去封装不同类型的返回后果集;
我的项目构造
### 须要用到的依赖
<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> |
自定义框架
客户端
##### 步骤
-
首先定义数据库配置文件, 配置引入的 sqlMapper 文件
应用字节输出流将其加载到内存中, 应用过 dom4j 解析封装成 Configuration 对象, 重复使用;
-
定义 sqlMapper 文件 用来编写 sql 语句, 入参, 出参类型
加载解析封装对象 MappedStatement 用来保留每个 sqlMapper 每条 sql 语句的入参, 出参类型以及 sql 操作类型;
定义数据库配置文件
首先咱们定义数据库配置文件sqlMapConfigration.xml
<configuration> | |
<!-- 数据库连贯信息 --> | |
<property name="driverClass" value="com.mysql.jdbc.Driver"></property> | |
<property name="jdbcUrl" value="jdbc:mysql:///stu_test"></property> | |
<property name="username" value="root"></property> | |
<property name="password" value="root"></property> | |
<!-- 配置 mapper sql 信息文件 会有多个 --> | |
<mapper resource="mapper.xml"></mapper> | |
</configuration> |
定义 Configration
类
用来保留 数据库配置信息 和 每个 mapper 中 sql 惟一类型 namespace.sql 的 id
public class Configration { | |
/** | |
* 数据源对象 | |
*/ | |
private DataSource dataSource; | |
/** | |
* key 规定是 namesapc + . + id(每个 sql 语句的 id) 设置参数以及返回类型时候应用 | |
*/ | |
private Map<String,MappedStatement> mapperStamentMap = new HashMap<>(); | |
public DataSource getDataSource() {return dataSource;} | |
public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;} | |
public Map<String, MappedStatement> getMapperStamentMap() {return mapperStamentMap;} | |
public void setMapperStamentMap(Map<String, MappedStatement> mapperStamentMap) {this.mapperStamentMap = mapperStamentMap;} | |
} |
定义 xmlConfigerBuilder
类解析
定义查问语句 sql 配置文件
mapper.xml
定义 sql 以及 sql 入参对象类型,sql 查问返回类型
<mapper namespace="User"> | |
<select id="selectOne" paramterType="com.udeam.com.udeam.pojo.User" resultType="com.udeam.com.udeam.pojo.User"> | |
select * from user where id = #{id} and name =#{name} | |
</select> | |
<select id="selectList" resultType="com.udeam.com.udeam.pojo.User"> | |
select * from user | |
</select> | |
</mapper> |
定义 MappedStatement 实体类
保留每个 mapper 中 sql 的 sql 语句类型 , sql 入参, 返回类型以及 sql 的 id
public class MappedStatement { | |
/** | |
* sql xml 语句 id 示意每条 sql 的唯一性 | |
*/ | |
private String id; | |
/** | |
* sql 入参类型 | |
*/ | |
private Class<?> paramType; | |
/** | |
* sql 返回类型 | |
*/ | |
private Class<?> resultType; | |
/** | |
* sql 语句 | |
*/ | |
private String sql; | |
public String getId() {return id;} | |
public void setId(String id) {this.id = id;} | |
public Class<?> getParamType() {return paramType;} | |
public void setParamType(Class<?> paramType) {this.paramType = paramType;} | |
public Class<?> getResultType() {return resultType;} | |
public void setResultType(Class<?> resultType) {this.resultType = resultType;} | |
public String getSql() {return sql;} | |
public void setSql(String sql) {this.sql = sql;} | |
} |
定义 SqlSessionFactoryBuilder
类build()
办法解析 sqlMapConfigration.xml
public class SqlSessionFactoryBuilder { | |
private Configration configration; | |
public SqlSessionFactoryBuilder() {this.configration = new Configration(); | |
} | |
public SqlSessionFactory build(InputStream inputStream) throws Exception { | |
//1 解析配置文件, 封装 Configuration | |
xmlConfigerBuilder xmlConfigerBuilder = new xmlConfigerBuilder(configration); | |
//2 解析 | |
configration = xmlConfigerBuilder.parseConfigration(inputStream); | |
// 3 创立 SqlSessionFactory | |
SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configration); | |
return sqlSessionFactory; | |
} | |
} |
定义 xmlConfigerBuilder
类用于
public class xmlConfigerBuilder { | |
private Configration configration; | |
public xmlConfigerBuilder(Configration configration) {this.configration = configration;} | |
/** | |
* 解析封装 xml | |
* @return | |
*/ | |
public Configration parseConfigration(InputStream inputStream) throws Exception {Document read = new SAXReader().read(inputStream); | |
// 获取跟标签 | |
Element rootElement = read.getRootElement(); | |
//1 设置 datasource 属性 | |
List<Element> elementList = rootElement.selectNodes("//property"); | |
Properties properties = new Properties(); | |
for (int i = 0; i < elementList.size(); i++) { | |
// 设置属性值 | |
String name = elementList.get(i).attributeValue("name"); | |
String value = elementList.get(i).attributeValue("value"); | |
properties.setProperty(name,value); | |
} | |
// 设置连接池属性 , 这里应用 c3p0 连接池 | |
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); | |
comboPooledDataSource.setUser(properties.getProperty("username")); | |
comboPooledDataSource.setPassword(properties.getProperty("password")); | |
comboPooledDataSource.setDriverClass(properties.getProperty("driverClass")); | |
comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl")); | |
// 设置 datasource | |
configration.setDataSource(comboPooledDataSource); | |
//2 封装解析 mapper 属性 | |
// 读取 mapper 设置 mapper 返回类型 以及 sql 等封装 MappedStatement 对象 | |
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configration); | |
List<Element> list = rootElement.selectNodes("//mapper"); | |
for (Element element : list) {String resource = element.attributeValue("resource"); | |
InputStream inputStream1 = Resource.inputStream(resource); | |
// 读取每一个 mapper xml | |
xmlMapperBuilder.parse(inputStream1); | |
} | |
return configration; | |
} | |
} |
定义 XMLMapperBuilder
类解析 Mapper 类中信息, 解析 Mapper 封装配置类中每个 sql 的 sql 语句以及返回类型, 入参类型
解析 mapper 封装 sql 语句属性到 MappedStatement
public class XMLMapperBuilder { | |
private Configration configration; | |
public XMLMapperBuilder(Configration configration) {this.configration = configration;} | |
public void parse(InputStream inputStream1) throws DocumentException, ClassNotFoundException {Document read = new SAXReader().read(inputStream1); | |
Element rootElement = read.getRootElement(); | |
// 获取 namespace | |
String namespace = rootElement.attributeValue("namespace"); | |
// 读取每一个查问标签 | |
List<Element> list = rootElement.selectNodes("//select"); | |
for (Element element : list) {MappedStatement mappedStatement = new MappedStatement(); | |
// 获取 sql | |
String sql = element.getTextTrim(); | |
mappedStatement.setSql(sql); | |
// 设置 id | |
String id = element.attributeValue("id"); | |
mappedStatement.setId(id); | |
// 设置入参类型 | |
String paramterType = element.attributeValue("paramterType"); | |
mappedStatement.setParamType(getClassType(paramterType)); | |
// 设置返回类型 | |
String resultType = element.attributeValue("resultType"); | |
mappedStatement.setResultType(getClassType(resultType)); | |
// 设置 mapperStamentMap | |
configration.getMapperStamentMap().put(namespace + "." + id, mappedStatement); | |
} | |
} | |
public Class<?> getClassType(String type) throws ClassNotFoundException {if(type==null){return null;} | |
Class<?> clasz = Class.forName(type); | |
return clasz; | |
} | |
} |
定义 Resource
类读取 xml
public class Resource { | |
/** | |
* 加载配置文件工具类 | |
* @param name | |
* @return | |
* @throws FileNotFoundException | |
*/ | |
public static InputStream inputStream(String name) throws Exception { | |
// 应用类加载器加载配置文件 | |
InputStream inputStream = Resource.class.getClassLoader().getResourceAsStream(name); | |
return inputStream; | |
} | |
} |
查问
定义查问接口 SqlSession
public interface SqlSession { | |
// 查问多个 | |
public <E>List<E> selectList(String statementId,Object...params) throws IllegalAccessException, IntrospectionException, InstantiationException, SQLException, InvocationTargetException, NoSuchFieldException; | |
// 查问单个 | |
public <T> T selectOne(String statementId,Object...params) throws IllegalAccessException, IntrospectionException, InstantiationException, SQLException, InvocationTargetException, NoSuchFieldException; | |
public void close() throws Exception;} |
sql 语句执行器 Excutor
接口
public interface Excutor { | |
/** | |
* 查问接口 | |
* @param configration 数据库配置类 | |
* @param mappedStatement mapper 信息对象 | |
* @param params 参数 | |
* @param <E> | |
* @return | |
*/ | |
<E> List<E> query(Configration configration, MappedStatement mappedStatement,Object[] params) throws SQLException, IllegalAccessException, InstantiationException, NoSuchFieldException, IntrospectionException, InvocationTargetException; | |
void close() throws Exception;} |
sql 语句执行器实现类
具体的 sql 执行器 (mybatis 中有三个), 默认的是 SimpleExcutor
这里面对通过传入的配置文件以及具体的 key(namespace.sql id)从 MappedStatement 获取 sql 以及 sql 入参返回类型
而后通过反射区设置参数, 获取后果返回集;
其中 BoundSql
类是对 xml 中 sql 进行解决, 将其转换为 ?
占位符, 解析出 #{}外面的值进行存储, 而后再去执行后续的赋值操作!
public class SimpleExcutor implements Excutor { | |
private Connection connection; | |
@Override | |
public <E> List<E> query(Configration configration, MappedStatement mappedStatement, Object[] params) throws SQLException, IllegalAccessException, InstantiationException, NoSuchFieldException, IntrospectionException, InvocationTargetException { | |
//1 获取连贯 | |
connection = configration.getDataSource().getConnection(); | |
//2 获取 sql select * from user where id = #{id} and name = #{name} | |
String sql = mappedStatement.getSql(); | |
// 对 sql 进行解决 // 转换 sql 语句:select * from user where id = ? and name = ?,转换的过程中,还须要对 #{}外面的值进行解析存储 | |
BoundSql boundSql = getBoundSql(sql); | |
// 最终的 sql | |
String finalSql = boundSql.getSqlText(); | |
// 3 预编译对象 | |
PreparedStatement preparedStatement = connection.prepareStatement(finalSql); | |
// 获取传入的参数类型 | |
Class<?> paramType = mappedStatement.getParamType(); | |
// 4 获取传入参数 | |
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList(); | |
// 设置参数 | |
for (int i = 0; i < parameterMappingList.size(); i++) {String content = parameterMappingList.get(i).getContent(); | |
// 反射设置值 | |
Field declaredField = paramType.getDeclaredField(content); | |
// 强制拜访 | |
declaredField.setAccessible(true); | |
Object o = declaredField.get(params[0]); | |
// 占位符设置值 列是从 1 开始的 | |
preparedStatement.setObject(i + 1, o); | |
System.out.println("以后属性是" + content + "值是 :" + o); | |
} | |
// 5. 执行 sql | |
ResultSet resultSet = preparedStatement.executeQuery(); | |
// 返回的参数类型 | |
Class<?> resultType = mappedStatement.getResultType(); | |
ArrayList<E> objects = new ArrayList<>(); | |
while (resultSet.next()) { | |
// 创建对象 | |
Object o = (E) resultType.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); | |
// 应用内省技术 也能够应用反射技术 | |
// 创立属性形容器,为属性生成读写办法 | |
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultType); | |
// 获取写办法 | |
Method writeMethod = propertyDescriptor.getWriteMethod(); | |
// 向类中写入值 | |
writeMethod.invoke(o, value); | |
} | |
objects.add((E) o); | |
} | |
return objects; | |
} | |
@Override | |
public void close() throws Exception {connection.close(); | |
} | |
/** | |
* 实现对 #{}的解析工作:1. 将#{}应用?进行代替,2. 解析出#{}外面的值进行存储 | |
* @param sql | |
* @return | |
*/ | |
private BoundSql getBoundSql(String sql) { | |
// 标记解决类:配置标记解析器来实现对占位符的解析解决工作 | |
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler(); | |
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler); | |
// 解析进去的 sql | |
String parseSql = genericTokenParser.parse(sql); | |
//#{}外面解析进去的参数名称 | |
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings(); | |
BoundSql boundSql = new BoundSql(parseSql,parameterMappings); | |
return boundSql; | |
} | |
} |
具体的实现查问接口如 DefaultSqlSession
默认的 sqlsession 实现类(mybatis 中默认的 DefaultSqlSession)
public class DefaultSqlSession implements SqlSession { | |
private Configration configration; | |
//sql 执行器 | |
private Excutor simpleExcutor = new SimpleExcutor(); | |
public DefaultSqlSession(Configration configration) {this.configration = configration;} | |
@Override | |
public <E> List<E> selectList(String statementId, Object... params) throws IllegalAccessException, IntrospectionException, InstantiationException, SQLException, InvocationTargetException, NoSuchFieldException { | |
// 依据 statementId 获取 MappedStatement 对象 | |
MappedStatement mappedStatement = configration.getMapperStamentMap().get(statementId); | |
//sql 执行器 | |
List<Object> query = simpleExcutor.query(configration, mappedStatement, params); | |
return (List<E>) query; | |
} | |
@Override | |
public <T> T selectOne(String statementId, Object... params) throws IllegalAccessException, IntrospectionException, InstantiationException, SQLException, InvocationTargetException, NoSuchFieldException {List<Object> objects = selectList(statementId, params); | |
if (objects==null || objects.size() == 0){return null;} | |
if (objects.size()>1){throw new RuntimeException("存在多个值!"); | |
} | |
return (T) objects.get(0); | |
} | |
@Override | |
public void close() throws Exception {simpleExcutor.close(); | |
} | |
} |
定义 SqlSessionFactory
工厂用来生产不同的 sqlSession 去执行 sql
获取 SqlSession 示例 以及对象接口
public interface SqlSessionFactory {public SqlSession openSqlSession(); | |
} |
具体工厂实现类, 生产 sqlsession 对象执行增删改查操作
public class DefaultSqlSessionFactory implements SqlSessionFactory { | |
private Configration configration; | |
public DefaultSqlSessionFactory(Configration configration) {this.configration = configration;} | |
@Override | |
public SqlSession openSqlSession() {return new DefaultSqlSession(configration); | |
} |
解析的 #{id}成站位符 ?
工具类, 以及内省创建对象工具类
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();} | |
} |
ParameterMapping
public class ParameterMapping { | |
private String content; | |
public ParameterMapping(String content) {this.content = content;} | |
public String getContent() {return content;} | |
public void setContent(String content) {this.content = content;} | |
} |
ParameterMappingTokenHandler
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
public interface TokenHandler {String handleToken(String content); | |
} |
一般测试
String name = "sqlMapConfigration.xml"; | |
// 1 加载 xml 配置文件 | |
InputStream inputStream = Resource.inputStream(name); | |
// 2 解析配置文件 | |
// 初始化 Configration 初始化容器 mapperStamentMap 容器保留 mapper 中 sql 信息 | |
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); | |
// 3 创立会话 | |
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream); | |
SqlSession sqlSession = sqlSessionFactory.openSqlSession(); | |
//4 查问 | |
User user = new User(); | |
user.setId(2); | |
user.setName("小王"); | |
// 非代理模式 | |
User user1 = sqlSession.selectOne("User.selectOne", user); | |
System.out.println(user1); | |
List<User> usersList = sqlSession.selectList("User.selectList"); | |
System.out.println(usersList); |
这是应用 namespace.id 形式硬编码去查问, 但理论过程中咱们间接通过 service 层调用 dao 层 mapper 去查问执行的;
故此, 咱们须要想 mybatis 那样定义一个 mapper 接口类, 而后应用动静代理调用执行;
在 SqlSession 接口中定义一个 mapper 代理接口
/** | |
* Mapper 代理接口 | |
* @param mapperClass | |
* @param <T> | |
* @return | |
*/ | |
public <T> T getMapper(Class<?> mapperClass); |
UserMapper 创立
public interface UserMapper { | |
/** | |
* 查问所有 | |
* @return | |
*/ | |
List<User> selectList2(); | |
/** | |
* 查问单个 依据条件 | |
* @param user | |
* @return | |
*/ | |
User selectOne2(User user); | |
} |
新创建 mapper2.xml
<!--mapper 代理模式 | |
语句 id 必须和 mapper 中查问语句办法名保持一致 | |
namespace 必须是类的权限定命名 | |
起因是 JDK 动静代理中 无奈提供对应的 namespace 和查问语句配置 id | |
故此用办法名和 mapper 类的全限定命名进行应用 key 从获取 mapper 配置文件 sql 语句的入参, 返回类型; | |
--> | |
<mapper namespace="com.udeam.test.mapper.UserMapper"> | |
<!-- 示意查问单个 --> | |
<select id="selectOne2" paramterType="com.udeam.pojo.User" resultType="com.udeam.pojo.User"> | |
select * from user where id = #{id} and name = #{name} | |
</select> | |
<!-- 示意查问多个 --> | |
<select id="selectList2" resultType="com.udeam.pojo.User"> | |
select * from user | |
</select> | |
</mapper> |
而后在 SqlMapXml 里增加进去 mapper
<mapper resource="mapper2.xml"></mapper>
在子类中去实现这个代理办法
这里须要留神的是
- 在上面 jdk 代理外面, 咱们无奈拿到 xml 文件里
namespace 和 sql id
值 - 实际上应用 mybatis 时候,
mapper 代理的办法名
, 和mapper,xml
里的会保持一致
,namspacs 会应用该 mapper 的权限定名;
在 JDK 动静代理中应用办法名和全门路去从 Configration 封装的 map 对象去获取 xml 里的 sql 的入参类型和返回类型;
/** | |
* 应用 JDK 动静代理来执行 mapper | |
*/ | |
@Override | |
public <T> T getMapper(Class<?> mapperClass) {Object o = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() { | |
@Override | |
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { | |
// 获取 class 的权限定命名 | |
String className = method.getDeclaringClass().getName(); | |
// 获取办法名 | |
String name = method.getName(); | |
// 拼接 statementid 从 map 中获取 sql 入参类型, 返回类型 | |
String statementid = className + "." + name; | |
// 判断是否实现泛型类型参数化 | |
Type genericReturnType = method.getGenericReturnType(); | |
if (genericReturnType instanceof ParameterizedType) { | |
// 还是去执行查询方法 | |
return selectList(statementid, args); | |
} | |
return selectOne(statementid, args); | |
} | |
}); | |
return (T) o; | |
} |
mapper 代理测试
// 代理测试 | |
UserMapper userMapper = sqlSession.getMapper(UserMapper.class); | |
System.out.println(userMapper.selectList2()); |
以上仅仅是实现了单表的查问操作, 和传入的固定参数, 对动静 sql 和删除在 底部
源代码中实现了, 能够 下载
下来康康, 删除和新增根本实现形式一样;
用到的设计模式
- 工厂模式
在创立不同的 sqlSession 时进行应用, 能够抉择 new 不同的子类; - 代理模式
应用 JDK 动静代理对 Mapper 进行代理 - 构建者模式
在 SqlSessionFactoryBuilder 类中 build()办法中通过对 Configration 对象的属性构建;
代码地址
点击下载