前言
mybaits 在ORM 框架中,可算是半壁江山了,由於它是輕量級,半自動加載,靈活性和易拓展性。深受廣大公司的喜愛,所以我們程序開發也離不開mybatis 。但是我們有對mabtis 源碼進行研究嗎?或者想看但是不知道怎么看的苦惱嗎?
歸根結底,我們還是需要知道為什么會有mybatis ,mybatis 解決了什么問題?
想要知道mybatis 解決了什么問題,就要知道傳統的JDBC 操作存在哪些痛點才促使mybatis 的誕生。
我們帶着這些疑問,再來一步步學習吧。
原始JDBC 存在的問題
所以我們先來來看下原始JDBC 的操作:
我們知道最原始的數據庫操作。分為以下幾步:
1、獲取connection 連接
2、獲取preparedStatement
3、參數替代占位符
4、獲取執行結果resultSet
5、解析封裝resultSet 到對象中返回。
如下是原始JDBC 的查詢代碼,存在哪些問題?
public static void main(String[] args) {
String dirver="com.mysql.jdbc.Driver";
String url="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8";
String userName="root";
String password="123456";
Connection connection=null;
List<User> userList=new ArrayList<>();
try {
Class.forName(dirver);
connection= DriverManager.getConnection(url,userName,password);
String sql="select * from user where username=?";
PreparedStatement preparedStatement=connection.prepareStatement(sql);
preparedStatement.setString(1,"張三");
System.out.println(sql);
ResultSet resultSet=preparedStatement.executeQuery();
User user=null;
while(resultSet.next()){
user=new User();
user.setId(resultSet.getInt("id"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
userList.add(user);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (!userList.isEmpty()) {
for (User user : userList) {
System.out.println(user.toString());
}
}
}
小伙伴們發現了上面有哪些不友好的地方?
我這里總結了以下幾點:
1、數據庫的連接信息存在硬編碼,即是寫死在代碼中的。
2、每次操作都會建立和釋放connection 連接,操作資源的不必要的浪費。
3、sql 和參數存在硬編碼。
4、將返回結果集封裝成實體類麻煩,要創建不同的實體類,並通過set方法一個個的注入。
存在上面的問題,所以mybatis 就對上述問題進行了改進。
對於硬編碼,我們很容易就想到配置文件來解決。mybatis 也是這么解決的。
對於資源浪費,我們想到是用連接池,mybatis 也是這個解決的。
對於封裝結果集麻煩,我們想到是用JDK的反射機制,好巧,mybatis 也是這么解決的。
設計思路
既然如此,我們就來寫一個自定義吃持久層框架,來解決上述問題,當然是參照mybatis 的設計思路,這樣我們在寫完之后,再來看mybatis 的源碼就恍然大悟,這個地方這樣配置原來是因為這樣啊。
我們分為使用端和框架端兩部分。
使用端
我們在使用mybatis 的時候是不是需要使用SqlMapConfig.xml 配置文件,用來存放數據庫的連接信息,以及mapper.xml 的指向信息。mapper.xml 配置文件用來存放sql 信息。
所以我們在使用端來創建兩個文件SqlMapConfig.xml 和mapper.xml。
框架端
框架端要做哪些事情呢?如下:
1、獲取配置文件。也就是獲取到使用端的SqlMapConfig.xml 以及mapper.xml的 文件
2、解析配置文件。對獲取到的文件進行解析,獲取到連接信息,sql,參數,返回類型等等。這些信息都會保存在configuration 這個對象中。
3、創建SqlSessionFactory,目的是創建SqlSession的一個實例。
4、創建SqlSession ,用來完成上面原始JDBC 的那些操作。
那在SqlSession 中 進行了哪些操作呢?
1、獲取數據庫連接
2、獲取sql,並對sql 進行解析
3、通過內省,將參數注入到preparedStatement 中
4、執行sql
5、通過反射將結果集封裝成對象
使用端實現
好了,上面說了一下,大概的設計思路,主要也是仿照mybatis 主要的類實現的,保證類名一致,方便我們后面閱讀源碼。我們先來配置好使用端吧,我們創建一個maven 項目。
在項目中,我們創建一個User實體類
public class User {
private Integer id;
private String username;
private String password;
private String birthday;
//getter()和setter()方法
}
創建SqlMapConfig.xml 和Mapper.xml
SqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false"></property>
<property name="userName" value="root"></property>
<property name="password" value="123456"></property>
<mapper resource="UserMapper.xml">
</mapper>
</configuration>
可以看到我們xml 中就配置了數據庫的連接信息,以及mapper 一個索引。mybatis中的SqlMapConfig.xml 中還包含其他的標簽,只是豐富了功能而已,所以我們只用最主要的。
mapper.xml
是每個類的sql 都會生成一個對應的mapper.xml 。我們這里就用User 類來說吧,所以我們就創建一個UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="cn.quellanan.dao.UserDao">
<select id="selectAll" resultType="cn.quellanan.pojo.User">
select * from user
</select>
<select id="selectByName" resultType="cn.quellanan.pojo.User" paramType="cn.quellanan.pojo.User">
select * from user where username=#{username}
</select>
</mapper>
可以看到有點mybatis 里面文件的味道,有namespace表示命名空間,id 唯一標識,resultType 返回結果集的類型,paramType 參數的類型。
我們使用端先創建到這,主要是兩個配置文件,我們接下來看看框架端是怎么實現的。
加油哈哈。
框架端實現
框架端,我們按照上面的設計思路一步一步來。
獲取配置
怎么樣獲取配置文件呢?我們可以使用JDK自帶自帶的類Resources加載器來獲取文件。我們創建一個自定義Resource類來封裝一下:
import java.io.InputStream;
public class Resources {
public static InputStream getResources(String path){
//使用系統自帶的類Resources加載器來獲取文件。
return Resources.class.getClassLoader().getResourceAsStream(path);
}
}
這樣通過傳入路徑,就可以獲取到對應的文件流啦。
解析配置文件
上面獲取到了SqlMapConfig.xml 配置文件,我們現在來解析它。
不過在此之前,我們需要做一點准備工作,就是解析的內存放到什么地方?
所以我們來創建兩個實體類Mapper 和Configuration。
Mapper
Mapper 實體類用來存放使用端寫的mapper.xml 文件的內容,我們前面說了里面有.id、sql、resultType 和paramType .所以我們創建的Mapper實體如下:
public class Mapper {
private String id;
private Class<?> resultType;
private Class<?> parmType;
private String sql;
//getter()和setter()方法
}
這里我們為什么不添加namespace 的值呢?
聰明的你肯定發現了,因為mapper里面這些屬性表明每個sql 都對應一個mapper,而namespace 是一個命名空間,算是sql 的上一層,所以在mapper中暫時使用不到,就沒有添加了。
Configuration
Configuration 實體用來保存SqlMapConfig 中的信息。所以需要保存數據庫連接,我們這里直接用JDK提供的 DataSource。還有一個就是mapper 的信息。每個mapper 有自己的標識,所以這里采用hashMap來存儲。如下:
public class Configuration {
private DataSource dataSource;
HashMap <String,Mapper> mapperMap=new HashMap<>();
//getter()和setter方法
}
XmlMapperBuilder
做好了上面的准備工作,我們先來解析mapper 吧。我們創建一個XmlMapperBuilder 類來解析。通過dom4j 的工具類來解析XML 文件。我這里用的dom4j 依賴為:
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
思路:
1、獲取文件流,轉成document。
2、獲取根節點,也就是mapper。獲取根節點的namespace屬性值
3、獲取select 節點,獲取其id,sql,resultType,paramType
4、將select 節點的屬性封裝到Mapper 實體類中。
5、同理獲取update/insert/delete 節點的屬性值封裝到Mapper 中
6、通過namespace.id 生成key 值將mapper對象保存到Configuration實體中的HashMap 中。
7、返回 Configuration實體
代碼如下:
public class XmlMapperBuilder {
private Configuration configuration;
public XmlMapperBuilder(Configuration configuration){
this.configuration=configuration;
}
public Configuration loadXmlMapper(InputStream in) throws DocumentException, ClassNotFoundException {
Document document=new SAXReader().read(in);
Element rootElement=document.getRootElement();
String namespace=rootElement.attributeValue("namespace");
List<Node> list=rootElement.selectNodes("//select");
for (int i = 0; i < list.size(); i++) {
Mapper mapper=new Mapper();
Element element= (Element) list.get(i);
String id=element.attributeValue("id");
mapper.setId(id);
String paramType = element.attributeValue("paramType");
if(paramType!=null && !paramType.isEmpty()){
mapper.setParmType(Class.forName(paramType));
}
String resultType = element.attributeValue("resultType");
if (resultType != null && !resultType.isEmpty()) {
mapper.setResultType(Class.forName(resultType));
}
mapper.setSql(element.getTextTrim());
String key=namespace+"."+id;
configuration.getMapperMap().put(key,mapper);
}
return configuration;
}
}
上面我只解析了select 標簽。大家可以解析對應insert/delete/uupdate 標簽,操作都是一樣的。
XmlConfigBuilder
我們再來解析一下SqlMapConfig.xml 配置信息思路是一樣的,
1、獲取文件流,轉成document。
2、獲取根節點,也就是configuration。
3、獲取根節點中所有的property 節點,並獲取值,也就是獲取數據庫連接信息
4、創建一個dataSource 連接池
5、將連接池信息保存到Configuration實體中
6、獲取根節點的所有mapper 節點
7、調用XmlMapperBuilder 類解析對應mapper 並封裝到Configuration實體中
8、完
代碼如下:
public class XmlConfigBuilder {
private Configuration configuration;
public XmlConfigBuilder(Configuration configuration){
this.configuration=configuration;
}
public Configuration loadXmlConfig(InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {
Document document=new SAXReader().read(in);
Element rootElement=document.getRootElement();
//獲取連接信息
List<Node> propertyList=rootElement.selectNodes("//property");
Properties properties=new Properties();
for (int i = 0; i < propertyList.size(); i++) {
Element element = (Element) propertyList.get(i);
properties.setProperty(element.attributeValue("name"),element.attributeValue("value"));
}
//是用連接池
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(properties.getProperty("driverClass"));
dataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
dataSource.setUser(properties.getProperty("userName"));
dataSource.setPassword(properties.getProperty("password"));
configuration.setDataSource(dataSource);
//獲取mapper 信息
List<Node> mapperList=rootElement.selectNodes("//mapper");
for (int i = 0; i < mapperList.size(); i++) {
Element element= (Element) mapperList.get(i);
String mapperPath=element.attributeValue("resource");
XmlMapperBuilder xmlMapperBuilder = new XmlMapperBuilder(configuration);
configuration=xmlMapperBuilder.loadXmlMapper(Resources.getResources(mapperPath));
}
return configuration;
}
}
創建SqlSessionFactory
完成解析后我們創建SqlSessionFactory 用來創建Sqlseesion 的實體,這里為了盡量還原mybatis 設計思路,也也采用的工廠設計模式。
SqlSessionFactory 是一個接口,里面就一個用來創建SqlSessionf的方法。
如下:
public interface SqlSessionFactory {
public SqlSession openSqlSession();
}
單單這個接口是不夠的,我們還得寫一個接口的實現類,所以我們創建一個DefaultSqlSessionFactory。
如下:
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
public SqlSession openSqlSession() {
return new DefaultSqlSeeion(configuration);
}
}
可以看到就是創建一個DefaultSqlSeeion並將包含配置信息的configuration 傳遞下去。DefaultSqlSeeion 就是SqlSession 的一個實現類。
創建SqlSession
在SqlSession 中我們就要來處理各種操作了,比如selectList,selectOne,insert.update,delete 等等。
我們這里SqlSession 就先寫一個selectList 方法。
如下:
public interface SqlSession {
/**
* 條件查找
* @param statementid 唯一標識,namespace.selectid
* @param parm 傳參,可以不傳也可以一個,也可以多個
* @param <E>
* @return
*/
public <E> List<E> selectList(String statementid,Object...parm) throws Exception;
然后我們創建DefaultSqlSeeion 來實現SqlSeesion 。
public class DefaultSqlSeeion implements SqlSession {
private Configuration configuration;
private Executer executer=new SimpleExecuter();
public DefaultSqlSeeion(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <E> List<E> selectList(String statementid, Object... parm) throws Exception {
Mapper mapper=configuration.getMapperMap().get(statementid);
List<E> query = executer.query(configuration, mapper, parm);
return query;
}
}
我們可以看到DefaultSqlSeeion 獲取到了configuration,並通過statementid 從configuration 中獲取mapper。 然后具體實現交給了Executer 類來實現。我們這里先不管Executer 是怎么實現的,就假裝已經實現了。那么整個框架端就完成了。通過調用Sqlsession.selectList() 方法,來獲取結果。
感覺我們都還沒有處理,就框架搭建好了?騙鬼呢,確實前面我們從獲取文件解析文件,然后創建工廠。都是做好准備工作。下面開始我們JDBC的實現。
SqlSession 具體實現
我們前面說SqlSeesion 的具體實現有下面5步
1、獲取數據庫連接
2、獲取sql,並對sql 進行解析
3、通過內省,將參數注入到preparedStatement 中
4、執行sql
5、通過反射將結果集封裝成對象
但是我們在DefaultSqlSeeion 中將實現交給了Executer來執行。所以我們就要在Executer中來實現這些操作。
我們首先來創建一個Executer 接口,並寫一個DefaultSqlSeeion中調用的query 方法。
public interface Executer {
<E> List<E> query(Configuration configuration,Mapper mapper,Object...parm) throws Exception;
}
接着我們寫一個SimpleExecuter 類來實現Executer 。
然后SimpleExecuter.query()方法中,我們一步一步的實現。
獲取數據庫連接
因為數據庫連接信息保存在configuration,所以直接獲取就好了。
//獲取連接
connection=configuration.getDataSource().getConnection();
獲取sql,並對sql 進行解析
我們這里想一下,我們在Usermapper.xml寫的sql 是什么樣子?
select * from user where username=#{username}
{username} 這樣的sql 我們改怎么解析呢?
分兩步
1、將sql 找到#{***},並將這部分替換成 ?號
2、對 #{***} 進行解析獲取到里面的參數對應的paramType 中的值。
具體實現用到下面幾個類。
GenericTokenParser類,可以看到有三個參數,開始標記,就是我們的“#{” ,結束標記就是 “}”, 標記處理器就是處理標記里面的內容也就是username。
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) {
//具體實現
}
}
主要的就是parse() 方法,用來獲取操作1 的sql。獲取結果例如:
select * from user where username=?
那上面用到TokenHandler 來處理參數。
ParameterMappingTokenHandler實現TokenHandler的類
public class ParameterMappingTokenHandler implements TokenHandler {
private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
// context是參數名稱 #{id} #{username}
@Override
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;
}
}
可以看到將參數名稱存放 ParameterMapping 的集合中了。
ParameterMapping 類就是一個實體,用來保存參數名稱的。
public class ParameterMapping {
private String content;
public ParameterMapping(String content) {
this.content = content;
}
//getter()和setter() 方法。
}
所以我們在我們通過GenericTokenParser類,就可以獲取到解析后的sql,以及參數名稱。我們將這些信息封裝到BoundSql實體類中。
public class BoundSql {
private String sqlText;
private List<ParameterMapping> parameterMappingList=new ArrayList<>();
public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {
this.sqlText = sqlText;
this.parameterMappingList = parameterMappingList;
}
////getter()和setter() 方法。
}
好了,那么分兩步走,先獲取,后解析
獲取
獲取原始sql 很簡單,sql 信息就存在mapper 對象中,直接獲取就好了。
String sql=mapper.getSql()
解析
1、創建一個ParameterMappingTokenHandler 處理器
2、創建一個GenericTokenParser 類,並初始化開始標記,結束標記,處理器
3、執行genericTokenParser.parse(sql);獲取解析后的sql‘’,以及在parameterMappingTokenHandler 中存放了參數名稱的集合。
4、將解析后的sql 和參數封裝到BoundSql 實體類中。
/**
* 解析自定義占位符
* @param sql
* @return
*/
private BoundSql getBoundSql(String sql){
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("#{","}",parameterMappingTokenHandler);
String parse = genericTokenParser.parse(sql);
return new BoundSql(parse,parameterMappingTokenHandler.getParameterMappings());
}
將參數注入到preparedStatement 中
上面的就完成了sql,的解析,但是我們知道上面得到的sql 還是包含 JDBC的 占位符,所以我們需要將參數注入到preparedStatement 中。
1、通過boundSql.getSqlText()獲取帶有占位符的sql.
2、接收參數名稱集合 parameterMappingList
3、通過mapper.getParmType() 獲取到參數的類。
4、通過getDeclaredField(content)方法獲取到參數類的Field。
5、通過Field.get() 從參數類中獲取對應的值
6、注入到preparedStatement 中
BoundSql boundSql=getBoundSql(mapper.getSql());
String sql=boundSql.getSqlText();
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
//獲取preparedStatement,並傳遞參數值
PreparedStatement preparedStatement=connection.prepareStatement(sql);
Class<?> parmType = mapper.getParmType();
for (int i = 0; i < parameterMappingList.size(); i++) {
ParameterMapping parameterMapping = parameterMappingList.get(i);
String content = parameterMapping.getContent();
Field declaredField = parmType.getDeclaredField(content);
declaredField.setAccessible(true);
Object o = declaredField.get(parm[0]);
preparedStatement.setObject(i+1,o);
}
System.out.println(sql);
return preparedStatement;
執行sql
其實還是調用JDBC 的executeQuery()方法或者execute()方法
//執行sql
ResultSet resultSet = preparedStatement.executeQuery();
通過反射將結果集封裝成對象
在獲取到resultSet 后,我們進行封裝處理,和參數處理是類似的。
1、創建一個ArrayList
2、獲取返回類型的類
3、循環從resultSet中取數據
4、獲取屬性名和屬性值
5、創建屬性生成器
6、為屬性生成寫方法,並將屬性值寫入到屬性中
7、將這條記錄添加到list 中
8、返回list
/**
* 封裝結果集
* @param mapper
* @param resultSet
* @param <E>
* @return
* @throws Exception
*/
private <E> List<E> resultHandle(Mapper mapper,ResultSet resultSet) throws Exception{
ArrayList<E> list=new ArrayList<>();
//封裝結果集
Class<?> resultType = mapper.getResultType();
while (resultSet.next()) {
ResultSetMetaData metaData = resultSet.getMetaData();
Object o = resultType.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,resultType);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(o,value);
}
list.add((E) o);
}
return list;
}
創建SqlSessionFactoryBuilder
我們現在來創建一個SqlSessionFactoryBuilder 類,來為使用端提供一個人口。
public class SqlSessionFactoryBuilder {
private Configuration configuration;
public SqlSessionFactoryBuilder(){
configuration=new Configuration();
}
public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {
XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder(configuration);
configuration=xmlConfigBuilder.loadXmlConfig(in);
SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return sqlSessionFactory;
}
}
可以看到就一個build 方法,通過SqlMapConfig的文件流將信息解析到configuration,創建並返回一個sqlSessionFactory 。
到此,整個框架端已經搭建完成了,但是我們可以看到,只實現了select 的操作,update、inster、delete 的操作我們在我后面提供的源碼中會有實現,這里只是將整體的設計思路和流程。
測試
終於到了測試的環節啦。我們前面寫了自定義的持久層,我們現在來測試一下能不能正常的使用吧。
見證奇跡的時刻到啦
我們先引入我們自定義的框架依賴。以及數據庫和單元測試
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>cn.quellanan</groupId>
<artifactId>myself-mybatis</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
然后我們寫一個測試類
1、獲取SqlMapperConfig.xml的文件流
2、獲取Sqlsession
3、執行查找操作
@org.junit.Test
public void test() throws Exception{
InputStream inputStream= Resources.getResources("SqlMapperConfig.xml");
SqlSession sqlSession = new SqlSessionFactoryBuilder().build(inputStream).openSqlSession();
List<User> list = sqlSession.selectList("cn.quellanan.dao.UserDao.selectAll");
for (User parm : list) {
System.out.println(parm.toString());
}
System.out.println();
User user=new User();
user.setUsername("張三");
List<User> list1 = sqlSession.selectList("cn.quellanan.dao.UserDao.selectByName", user);
for (User user1 : list1) {
System.out.println(user1);
}
}
可以看到已經可以了,看來我們自定義的持久層框架生效啦。
優化
但是不要高興的太早哈哈,我們看上面的測試方法,是不是感覺和平時用的不一樣,每次都都寫死statementId ,這樣不太友好,所以我們接下來來點騷操作,通用mapper 配置。
我們在SqlSession中增加一個getMapper方法,接收的參數是一個類。我們通過這個類就可以知道statementId .
/**
* 使用代理模式來創建接口的代理對象
* @param mapperClass
* @param <T>
* @return
*/
public <T> T getMapper(Class<T> mapperClass);
具體實現就是利用JDK 的動態代理機制。
1、通過Proxy.newProxyInstance() 獲取一個代理對象
2、返回代理對象
那代理對象執行了哪些操作呢?
創建代理對象的時候,會實現一個InvocationHandler接口,重寫invoke() 方法,讓所有走這個代理的方法都會執行這個invoke() 方法。那這個方法做了什么操作?
這個方法就是通過傳入的類對象,獲取到對象的類名和方法名。用來生成statementid 。所以我們在mapper.xml 配置文件中的namespace 就需要制定為類路徑,以及id 為方法名。
實現方法:
@Override
public <T> T getMapper(Class<T> mapperClass) {
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSeeion.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//獲取到方法名
String name = method.getName();
//類型
String className = method.getDeclaringClass().getName();
String statementid=className+"."+name;
return selectList(statementid,args);
}
});
return (T) proxyInstance;
}
我們寫一個UserDao
public interface UserDao {
List<User> selectAll();
List<User> selectByName(User user);
}
這個是不是我們熟悉的味道哈哈,就是mapper層的接口。
然后我們在mapper.xml 中指定namespace 和id
接下來我們在寫一個測試方法
@org.junit.Test
public void test2() throws Exception{
InputStream inputStream= Resources.getResources("SqlMapperConfig.xml");
SqlSession sqlSession = new SqlSessionFactoryBuilder().build(inputStream).openSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> users = mapper.selectAll();
for (User user1 : users) {
System.out.println(user1);
}
User user=new User();
user.setUsername("張三");
List<User> users1 = mapper.selectByName(user);
for (User user1 : users1) {
System.out.println(user1);
}
}
番外
自定義的持久層框架,我們就寫完了。這個實際上就是mybatis 的雛形,我們通過自己手動寫一個持久層框架,然后在來看mybatis 的源碼,就會清晰很多。下面這些類名在mybatis 中都有體現。
這里拋磚引玉,祝君閱讀源碼愉快。
覺得有用的兄弟們記得收藏啊。
厚顏無恥的求波點贊!!!
本文由博客一文多發平台 OpenWrite 發布!