mybatis原始碼學習之自定義持久層框架
- 學習目標
- jdbc代碼回顧,到底出現了哪些問題
- 如何自定義一個持久層框架
- 使用端
- 核心組態檔
- 映射組態檔
- 專案本身(對jdbc代碼的一個封裝處理)
- 加載組態檔
- 創建容器物件
- 決議組態檔
- 創建會話工廠介面以及實作類
- 創建會話介面以及實作類
- 創建一個執行器以及實作類
- 剩余代碼檔案
- 測驗
- 回顧
- 專案優化
- 動態代理
- 創建dao層
- 實作動態代理
- 回到測驗
- 學習識訓
學習目標
已經有jdbc了,為什么會出現mybatis等各種持久層框架
怎樣自定義一個持久層框架
通過自定義持久層框架深入理解mybatis原始碼
jdbc代碼回顧,到底出現了哪些問題
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try{
//1 加載資料庫驅動
Class.forName("com.mysql.jdbc.Driver");
//2 獲取資料庫連接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis_test?characterEncoding=utf-8", "root", "123456");
//3 定義sql陳述句
String sql = "select * from t_user where user_name = ?";
//4 引數處理
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"杰克");
//5 獲取結果集
resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
Long userId = resultSet.getLong("user_id");
String userName = resultSet.getString("user_name");
User user = new User();
user.setUserId(userId);
user.setUserName(userName);
System.out.println(user);
}
//6 封裝結果集
}catch (Exception e){
e.printStackTrace();
}finally {
// 7 釋放
try{
resultSet.close();
preparedStatement.close();
connection.close();
}catch (SQLException e){
e.printStackTrace();
}
}
通過觀察jdbc代碼我們找到了問題以及解決辦法
| 問題 | 解決辦法 |
|---|---|
| 資料庫配置資訊以及sql存在硬編碼 | 通過組態檔解決 |
| jdbc放在持久層會被反復呼叫,頻繁創建釋放資料庫鏈接 | 連接池解決 |
| 手動封裝結果集,繁瑣 | 反射內省解決 |
如何自定義一個持久層框架
將持久層框架看做一個專案,那么只需要考慮兩點:
1使用端 2專案本身的實作
專案目的:封裝jdbc并解決其產生的問題
使用端
提供兩個配置資訊:
- 資料庫配置資訊(核心配置):命名mapper-config.xml
- 存放資料庫的配置資訊,存放mapper.xml的路徑
為何要存放mapper.xml的路徑?記住這個問題
- sql配置資訊(映射配置):*Mapper.xml
- 存放sql的配置資訊
核心組態檔
<!-- 創建一個父節點 -->
<configuration>
<!-- 資料庫配置資訊-->
<dataSource>
<property name="driver" value="com.mysql.jdbc.Driver"></property>
<property name="jdbc" value="jdbc:mysql:///mybatis_test"></property>
<property name="name" value="root"></property>
<property name="password" value="123456"></property>
</dataSource>
<!-- 存放mapper.xml的全路徑 -->
<mapper resource="mappers/UserMapper.xml"></mapper>
</configuration>
映射組態檔
<!-- namespace 用于區分多個mapper -->
<mapper namespace="user">
<!-- namespace.id 組成sql的唯一標識 叫做statementId -->
<!-- resultType 回傳型別 查詢結果是什么,我們這邊是參考型別User
那么就要給出User的全路徑以供后續通過反射完成結果集的一個映射-->
<select id="selectList" resultType="com.hen84.entity.User">
select * from t_user
</select>
<!-- paramterType 引數型別,這里是多個引數所以是參考型別,同理給出全路徑-->
<!-- 定義一個#{} 用來獲取到傳入的引數是什么 然后將映射到的引數傳遞進去 -->
<select id="selectOne" resultType="com.hen84.entity.User" paramterType="com.hen84.entity.User">
select * from t_user
where user_id = #{userId}
and user_name = #{userName}
</select>
</mapper>
專案本身(對jdbc代碼的一個封裝處理)
- 加載組態檔
- 創建一個Resources類,方法InputSteam getResourceAsSteam(String path)
根據組態檔的路徑,加載組態檔成位元組輸入流,存放到記憶體中 - 回到上面問題為何要存放mapper.xml的路徑?
Mapper.xml這個配置,需求越多檔案也就越多,我們沒有必要多次呼叫 getResourceAsSteam來加載Mapper.xml,所以存放到mapper-config.xml中,一次加載全部獲取,
- 創建一個Resources類,方法InputSteam getResourceAsSteam(String path)
- 創建容器物件
- 存放的就是對組態檔決議出來的內容
Configuration:核心配置類:存放mapper-config.xml決議出來的內容
MappedStatement:映射配置類存放*Mapper.xml決議出來的內容
- 存放的就是對組態檔決議出來的內容
- 決議組態檔:
- 利用兩個模式,建造者模式以及工廠模式
創建一個構建者類:SqlSessionFactoryBuilder 方法build(InputSteam in) - 目的:生產sqlSession 會話物件
- 程序:使用dom4j決議組態檔,將決議的內容封裝到容器物件中,創建一個工廠SqlSessionFactory,生產sqlSession
- 利用兩個模式,建造者模式以及工廠模式
- 創建會話工廠介面以及實作類
- SqlSessionFactory
- DefaultSqlSessionFactory
- 方法:openSession() 生產sqlSession
- 創建會話介面以及實作類
- SqlSession
- DefaultSession
- 方法(對資料庫的操作):
- selectList();
- selectOne();
- update();
- delete();
- 創建一個執行器以及實作類
- Executor
- SimpleExecutor
- 方法query(Configuration,MappedStatement,Object… params)執行jdbc代碼
- Object… params 請求引數,無法確定引數數量,所以為可變
加載組態檔
package com.hen84.io;
import java.io.InputStream;
public class Resources {
// 傳入組態檔的路徑,將其決議成輸入流存到記憶體中
public static InputStream getResourceAsSteam(String path){
InputStream inputStream = Resources.class.getClassLoader().getResourceAsStream(path);
return inputStream;
}
}
創建容器物件
package com.hen84.entity;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
// 用來存放對mybatis-config.xml檔案的決議
public class Configuration {
// 資料庫配置資訊
DataSource dataSource;
// key: statementId :namespace.id 例如 user.selectList
// value: 封裝好的mappedStatement物件
Map<String,MappedStatement> mappedStatementMap = new HashMap<String, MappedStatement>();
}
package com.hen84.entity;
// 用來存放對*Mapper.cml檔案的決議
public class MappedStatement {
// id標識
private String id;
// 回傳值型別
private String resultType;
// 引數型別
private String paramterType;
// sql陳述句
private String sql;
}
決議組態檔
package com.hen84.test;
import com.hen84.io.Resources;
import com.hen84.sqlSession.SqlSessionFactory;
import com.hen84.sqlSession.SqlSessionFactoryBuilder;
import java.io.InputStream;
public class Test {
public void mybatisTest(){
try{
// 加載組態檔成位元組輸入流,存放到記憶體中
InputStream inputStream = Resources.getResourceAsSteam("mybatis-config.xml");
// 創建一個工廠SqlSessionFactory
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.builder(inputStream);
}catch (Exception e){
}
}
}
package com.hen84.sqlSession;
import com.hen84.config.XMLConfigBuilder;
import com.hen84.entity.Configuration;
import org.dom4j.DocumentException;
import java.beans.PropertyVetoException;
import java.io.InputStream;
// 創建一個SqlSessionFactory構造器
public class SqlSessionFactoryBuilder {
public static SqlSessionFactory builder(InputStream inputStream) throws DocumentException, PropertyVetoException {
// 使用dom4j決議組態檔,將決議的內容封裝到容器物件中
// 將此程序封裝
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
Configuration configuration = xmlConfigBuilder.parseConfig(inputStream);
// 創建一個工廠SqlSessionFactory物件
return null;
}
}
package com.hen84.config;
import com.hen84.entity.Configuration;
import com.hen84.io.Resources;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
public class XMLConfigBuilder {
private Configuration configuration;
public XMLConfigBuilder() {
this.configuration = new Configuration();
}
// 使用dom4j對核心組態檔進行決議,封裝到configuration中
public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
// dom4j將組態檔決議成一個document
Document document = new SAXReader().read(inputStream);
// getRootElement 拿到的就是 mybatis-config.xml中的父節點<configuration>
Element rootElement = document.getRootElement();
// selectNodes 全域查詢property節點 回傳一個list 這里拿到的就是資料庫配置資訊
List<Element> propertyList = rootElement.selectNodes("//property");
// 創建一個Properties 用來存放拿到的資料庫配置資訊
Properties properties = new Properties();
for (Element element : propertyList) {
// name value 標簽中的屬性
String name = element.attributeValue("name");
String value = element.attributeValue("value");
properties.setProperty(name,value);
}
// 使用 c3p9 連接池
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass(properties.getProperty("driver"));
comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbc"));
comboPooledDataSource.setUser(properties.getProperty("user"));
comboPooledDataSource.setPassword(properties.getProperty("password"));
// 核心配置資訊存放到其容器中
configuration.setDataSource(comboPooledDataSource);
// 決議mapper.xml
// 拿到路徑
List<Element> mapperList = rootElement.selectNodes("//mapper");
for (Element element : mapperList) {
String mapperResource = element.attributeValue("resource");
// 獲取位元組輸入流
InputStream mapperAsSteam = Resources.getResourceAsSteam(mapperResource);
// 再一次封裝決議 傳入已經決議好核心配置資訊的configuration物件
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
xmlMapperBuilder.parse(mapperAsSteam);
}
return configuration;
}
}
package com.hen84.config;
import com.hen84.entity.Configuration;
import com.hen84.entity.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
public class XMLMapperBuilder {
private Configuration configuration;
public XMLMapperBuilder(Configuration configuration) {
this.configuration = configuration;
}
// 使用dom4j對mapper組態檔進行決議,封裝到configuration的MappedStatementMap中
public void parse(InputStream inputStream) throws DocumentException {
Document document = new SAXReader().read(inputStream);
// 此處拿到的就是 UserMapper.xml中的<mapper>標簽
Element rootElement = document.getRootElement();
// 拿到namespace唯一屬性值
String nameSpace = rootElement.attributeValue("namespace");
// 獲取下面的所有select節點回傳list<Element>
List<Element> selectList = rootElement.selectNodes("//select");
for (Element element : selectList) {
String id = element.attributeValue("id");
String resultType = element.attributeValue("resultType");
String paramterTyoe = element.attributeValue("paramterTyoe");
String sqlText = element.getTextTrim();
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setId(id);
mappedStatement.setResultType(resultType);
mappedStatement.setParamterType(paramterType);
mappedStatement.setSql(sqlText);
// namespaces.id 組成sql的唯一標識 叫做statementId
String statementId = nameSpace + "." + id;
// 將拿到的mappedStatement存放到configuration的map中 statementId 就是唯一標識
configuration.getMappedStatementMap().put(statementId,mappedStatement);
}
}
}
創建會話工廠介面以及實作類
package com.hen84.sqlSession;
// 創建一個SqlSession工廠 生產SqlSession
public interface SqlSessionFactory {
// 生產sqlSession
public SqlSession openSession();
}
package com.hen84.sqlSession;
import com.hen84.entity.Configuration;
// 會話工廠介面的實作類
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration configuration;
// 創建會話工廠實體時將configuration帶入
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
// 生產sqlSession
public SqlSession openSession() {
return new DefaultSqlSession();
}
}
創建會話介面以及實作類
package com.hen84.sqlSession;
// 會話介面 介面有對資料庫的CRUD功能
public interface SqlSession {
}
package com.hen84.sqlSession;
// 會話介面的實作類
public class DefaultSqlSession implements SqlSession {
}
創建一個執行器以及實作類
package com.hen84.sqlSession;
import com.hen84.entity.Configuration;
import com.hen84.entity.MappedStatement;
import java.util.List;
// 執行器
public interface Executor {
// 執行方法 configuration配置資訊 mappedStatement 映射檔案資訊 params可變引數
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
}
package com.hen84.sqlSession;
import com.hen84.config.BoundSql;
import com.hen84.entity.Configuration;
import com.hen84.entity.MappedStatement;
import com.hen84.utils.GenericTokenParser;
import com.hen84.utils.ParameterMapping;
import com.hen84.utils.ParameterMappingTokenHandler;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class SimpleExecutor implements Executor {
// 執行sql
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
// 獲取資料庫連接
Connection connection = configuration.getDataSource().getConnection();
// 獲取sql 陳述句
String sql = mappedStatement.getSql();
// 轉換sql 陳述句 對#{}的值決議存盤
BoundSql boundSql = getBoundSql(sql);
// 通過 connection的prepareStatement方法 獲取預處理物件 preparedStatement
// 引數就是我們已經決議好的sql
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSql());
// 獲取到引數型別的全路徑
String paramterType = mappedStatement.getParamterType();
// 設定引數 params就是我們已經實體好的User
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
for (int i = 0 ; i < parameterMappings.size(); i++){
ParameterMapping parameterMapping = parameterMappings.get(i);
// content 就是#{}里面的值
String content = parameterMapping.getContent();
// 通過反射,完成引數的設定
Class<?> paramterTypeClass = getClassType(paramterType);
// 獲取到屬性物件
Field declaredField = paramterTypeClass.getDeclaredField(content);
// 暴力訪問
declaredField.setAccessible(true);
// 拿到引數物件
Object o = declaredField.get(params[0]);
preparedStatement.setObject(i+1,o);
}
// 執行sql 結果封裝在ResultSet中
ResultSet resultSet = preparedStatement.executeQuery();
ArrayList<Object> objects = new ArrayList<Object>();
String resultType = mappedStatement.getResultType();
Class<?> resultClass = getClassType(resultType);
// 封裝結果集
while (resultSet.next()){
// 獲取元資料
Object resultObject = resultClass.newInstance();
ResultSetMetaData metaData = resultSet.getMetaData();
for(int i = 1; i <= metaData.getColumnCount(); i++){
// 獲取欄位名
String columnName = metaData.getColumnName(i);
// 欄位的值
Object object = resultSet.getObject(columnName);
// 通過反射 完成物體類和資料庫表的映射 camelName 駝峰式命名下劃線轉換
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(camelName(columnName),resultClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
// resultObject 類的實體 object 欄位的值
writeMethod.invoke(resultObject, object);
}
objects.add(resultObject);
}
return (List<E>) objects;
}
// 決議sql陳述句 直接使用工具類 完成兩個操作
// 1 將#{}轉化為? 2將#{}的值進行存盤
public BoundSql getBoundSql(String sql){
// 標記處理類 : 完成對占位符的決議處理作業
ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
/**
* 標記決議器
* params: openToken 開始標記 也就是#{
* closeToken 結束標記也就是 }
* TokenHandler
*/
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", tokenHandler);
// parse 將我們的sql 傳入 最侄訓傳一個決議之后的sql
String parse = genericTokenParser.parse(sql);
// getParameterMappings()獲取道德ParameterMapping就是#{}里面的值的一個容器
// 比如 #{userId} 以及 #{userName}
List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings();
return new BoundSql(parse,parameterMappings);
}
// 通過反射,獲取到Class
public Class<?> getClassType(String paramterType) throws ClassNotFoundException {
if(paramterType != null) return Class.forName(paramterType);
throw new RuntimeException("ClassNotFoundException:" + paramterType);
}
/**
* 將下劃線大寫方式命名的字串轉換為駝峰式,如果轉換前的下劃線大寫方式命名的字串為空,則回傳空字串,
* 例如:HELLO_WORLD->HelloWorld
* @param name 轉換前的下劃線大寫方式命名的字串
* @return 轉換后的駝峰式命名的字串
*/
public static String camelName(String name) {
StringBuilder result = new StringBuilder();
// 快速檢查
if (name == null || name.isEmpty()) {
// 沒必要轉換
return "";
} else if (!name.contains("_")) {
// 不含下劃線,僅將首字母小寫
return name.substring(0, 1).toLowerCase() + name.substring(1);
}
// 用下劃線將原始字串分割
String camels[] = name.split("_");
for (String camel : camels) {
// 跳過原始字串中開頭、結尾的下換線或雙重下劃線
if (camel.isEmpty()) {
continue;
}
// 處理真正的駝峰片段
if (result.length() == 0) {
// 第一個駝峰片段,全部字母都小寫
result.append(camel.toLowerCase());
} else {
// 其他的駝峰片段,首字母大寫
result.append(camel.substring(0, 1).toUpperCase());
result.append(camel.substring(1).toLowerCase());
}
}
return result.toString();
}
}
剩余代碼檔案
package com.hen84.config;
import com.hen84.utils.ParameterMapping;
import java.util.ArrayList;
import java.util.List;
// 用來存放決議之后的sql 也就是將#{} 轉化為?之后的sql
public class BoundSql {
// 決議之后的sql
private String sql;
// #{}里面的值
private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
public BoundSql(String sql, List<ParameterMapping> parameterMappings) {
this.sql = sql;
this.parameterMappings = parameterMappings;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}
public void setParameterMappings(List<ParameterMapping> parameterMappings) {
this.parameterMappings = parameterMappings;
}
}
/**
* Copyright 2009-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hen84.utils;
/**
* @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();
}
}
package com.hen84.utils;
// 用來存盤#{} 里面的值的存盤
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;
}
}
package com.hen84.utils;
import java.util.ArrayList;
import java.util.List;
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;
}
}
/**
* Copyright 2009-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hen84.utils;
/**
* @author Clinton Begin
*/
public interface TokenHandler {
String handleToken(String content);
}
測驗
package com.hen84.test;
import com.hen84.entity.User;
import com.hen84.io.Resources;
import com.hen84.sqlSession.SqlSession;
import com.hen84.sqlSession.SqlSessionFactory;
import com.hen84.sqlSession.SqlSessionFactoryBuilder;
import java.io.InputStream;
import java.util.List;
public class Test {
public static void main(String[] args) {
try{
// 加載組態檔成位元組輸入流,存放到記憶體中
InputStream inputStream = Resources.getResourceAsSteam("mybatis-config.xml");
// 創建一個工廠SqlSessionFactory
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.builder(inputStream);
// 拿到會話物件
SqlSession sqlSession = sqlSessionFactory.openSession();
// 創建一個User物體 用于傳參
User user = new User();
user.setUserId(2L);
user.setUserName("胡歌");
// 呼叫查詢方法
List<User> resultUser = sqlSession.selectList("user.selectList",user);
// 呼叫單個查詢方法
User resultUserOne = sqlSession.selectOne("user.selectOne", user);
System.out.println(resultUser);
System.out.println(resultUserOne);
}catch (Exception e){
e.printStackTrace();
}
}
}
執行結果
[User{userId=1, userName='彭于晏', userPhone='10000009282'}, User{userId=2, userName='胡歌', userPhone='1999872625384'}]
User{userId=2, userName='胡歌', userPhone='1999872625384'}
回顧
到此算是大致OK了,但是還是會有兩個問題
- statementId還是存在硬編碼
專案優化
優化程序 :動態代理回傳代理物件
由代理物件去解決我們的硬編碼問題
動態代理
創建dao層
package com.hen84.dao;
import com.hen84.entity.User;
import java.util.List;
public interface UserMapper {
List<User> selectList();
User selectOne(User user);
}
實作動態代理
我們在SqlSession介面以及實作類加入getMapper的方法和實作
// 回傳動態代理的實作類 引數就是我們的dao
public <T> T getMapper(Class<?> mapperClass);
@Override
public <T> T getMapper(Class<?> mapperClass) {
// 使用JDK 動態代理 來為介面生成代理物件
/**
*
* newProxyInstance
* 引數:類加載器
* Class陣列
* InvocationHandler介面
* InvocationHandler介面實作里面有一個invoke方法
*
* invoke
* 引數: proxy 當前代理物件
* method 當前被呼叫方法
* args 傳遞的引數
*
*/
Object o = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 根據不同情況來呼叫selectList 或者selectOne
//準備引數
// 1、statementId
/**
* 這里我們考慮一個問題
* statementId 也就是mapper.xml中的namespace.id 我們目前沒法拿到
* 于是我們更改namespace的值為我們介面的全路徑 例如com.hen84.dao.UserMapper
* id 我們這里一定要和方法名保持一致
*/
// methodName 也就是id
String methodName = method.getName();
// className 也就是namespace
String className = method.getDeclaringClass().getName();
String statementId = className + "." + methodName;
// 2、params 就是args
// 如何來確定呼叫的是selectList 還是selectOne ? 回傳值型別
// 獲取被呼叫方法的回傳值型別
Type genericReturnType = method.getGenericReturnType();
// 判斷是否進行了泛型型別引數化
// 泛型型別引數化:當前回傳值型別是否含有泛型 有就是List
if(genericReturnType instanceof ParameterizedType){
List<Object> objects = selectList(statementId, args);
return objects;
}
return selectOne(statementId,args);
}
});
return (T) o;
}
回到測驗
public static void main(String[] args) {
try{
// 加載組態檔成位元組輸入流,存放到記憶體中
InputStream inputStream = Resources.getResourceAsSteam("mybatis-config.xml");
// 創建一個工廠SqlSessionFactory
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.builder(inputStream);
// 拿到會話物件
SqlSession sqlSession = sqlSessionFactory.openSession();
// 創建一個User物體 用于傳參
User user = new User();
user.setUserId(2L);
user.setUserName("胡歌");
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectList();
System.out.println(users);
User user1 = userMapper.selectOne(user);
System.out.println(user1);
}catch (Exception e){
e.printStackTrace();
}
}
列印結果
[User{userId=1, userName='彭于晏', userPhone='10000009282'}, User{userId=2, userName='胡歌', userPhone='1999872625384'}]
User{userId=2, userName='胡歌', userPhone='1999872625384'}
OK 到此自定義持久層框架就結束了
原始碼在最后
學習識訓
1、通過自定義持久層框架加深了對mybatis原始碼的理解
2、所有的持久層框架都是對JDBC的一個高級封裝
3、mybatis中的部分重要介面的作用
4、mybatis設計模式的一個深入理解
原始碼:
鏈接: https://pan.baidu.com/s/1Rk0CzAnVol11O3iIHlB3UA 密碼: o7s9
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/251751.html
標籤:java
下一篇:小紅書面試題:如何分析用戶行為?
