本文主要內(nèi)容摘抄自架構(gòu)師教程之手寫MyBatis框架【完】
雖然感覺他們有點(diǎn)不地道掂墓,程序關(guān)鍵代碼一閃而過(guò)不提供源碼湃累,視頻中間頻繁插入廣告,但也算讓我學(xué)到了點(diǎn)東西永部,在此表示感謝字管。
建議別看這篇文章啰挪,代碼可以看看,文章太亂了嘲叔,別看亡呵,拿著我的代碼去看視頻吧。代碼可以點(diǎn)擊這里下載硫戈。
手寫一個(gè)簡(jiǎn)易版的MyBatis框架:
1.讀取mybatis-config.xml配置文件
2.構(gòu)建SqlSessionFactory
3.打開SqlSession
4.獲取Mapper接口對(duì)象
5.調(diào)用Mapper接口對(duì)象的方法操作數(shù)據(jù)庫(kù)
準(zhǔn)備
MyBatis
MyBatis 是一款優(yōu)秀的持久層框架锰什,它支持自定義 SQL、存儲(chǔ)過(guò)程以及高級(jí)映射丁逝。MyBatis 免除了幾乎所有的 JDBC 代碼以及設(shè)置參數(shù)和獲取結(jié)果集的工作汁胆。MyBatis可以通過(guò)簡(jiǎn)單的 XML 或注解來(lái)配置和映射原始類型、接口和 Java POJO(Plain Old Java Objects霜幼,普通老式 Java 對(duì)象)為數(shù)據(jù)庫(kù)中的記錄嫩码。
我們的目的是手寫一個(gè)簡(jiǎn)易版MyBatis,我們不需要支持MyBatis的緩存罪既、動(dòng)態(tài)SQL铸题、高級(jí)映射,只要能把Mapper文件與Mapper接口相關(guān)聯(lián)并執(zhí)行Mapper中定義的SQL即可琢感。
項(xiàng)目開始之前我們先抽取下MyBatis執(zhí)行的大致過(guò)程丢间。
MyBatis大致執(zhí)行流程
一個(gè)普通的MyBatis項(xiàng)目大致由Mapper文件、Mapper接口驹针、MyBatis配置文件組成千劈,如下為MyBatis常用形式:
public void testMybatis () throws IOException {
// 獲取配置文件輸入流
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 通過(guò)SqlSessionFactoryBuilder的build()方法創(chuàng)建SqlSessionFactory實(shí)例
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 調(diào)用openSession()方法創(chuàng)建SqlSession實(shí)例
SqlSession sqlSession = sqlSessionFactory.openSession();
// 獲取UserMapper代理對(duì)象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 執(zhí)行Mapper方法,獲取執(zhí)行結(jié)果
List<UserEntity> userList = userMapper.listAllUser();
System.out.println(JSON.toJSONString(userList));
}
獲取配置文件
這個(gè)沒(méi)啥好說(shuō)的牌捷,讀取配置文件
構(gòu)建SqlSessionFactory
看這個(gè)類的名字就知道這個(gè)接口可以用于構(gòu)建SqlSession墙牌,這是一個(gè)接口,它有一個(gè)默認(rèn)實(shí)現(xiàn)DefaultSqlSessionFactory暗甥,其維護(hù)了Configuration對(duì)象:
// 將XML配置文件構(gòu)建為Configuration
private final Configuration configuration;
Configuration類十分重要喜滨,代表著MyBatis的配置項(xiàng):
它的Environment中的DataSource屬性則保存著數(shù)據(jù)源,比如用戶名撤防、密碼虽风、URL等
示例代碼通過(guò)SqlSessionFactoryBuilder的build方法讀取配置文件同時(shí)將Configuration構(gòu)建完成。
不斷debug可以看到寄月,SqlSessionFactoryBudiler的build方法會(huì)執(zhí)行XMLConfigBuilder的parse方法辜膝,這個(gè)方法返回Configuration。
parse方法解析配置文件configuration下的所有信息漾肮。
進(jìn)入parse方法可以后又調(diào)用parseConfiguration方法厂抖,這里有一個(gè)重要的mappersElement方法,mappersElement方法解析配置文件里的mappers信息克懊,通常這包含Mapper映射文件忱辅。
mappers解析完成后通過(guò)Configuration的addMapper方法加入到Configuraiton維護(hù)的MapperRegistry mapperRegistry 。
調(diào)用棧:
獲取SqlSession
SqlSessionFactory構(gòu)建完成后可以構(gòu)建SqlSession了谭溉,SqlSession是MyBatis暴露給外部使用的統(tǒng)一接口層墙懂。它有一個(gè)默認(rèn)實(shí)現(xiàn)DefaultSqlSession:
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
...
}
它持有Configuration和Executor的引用,Executor是真正操作數(shù)據(jù)庫(kù)的接口扮念。
SqlSessionFactory的openSession方法最后返回的正是DefaultSqlSession损搬。
獲取Mapper接口
顯然,Mapper接口不可能一個(gè)個(gè)實(shí)現(xiàn)柜与,所以MyBatis采用JDK自帶的動(dòng)態(tài)代理生成Mapper接口的代理對(duì)象并返回巧勤。
public class MapperProxy<T> implements InvocationHandler, Serializable
MapperProxy里的invoke方法就是具體的攔截邏輯。
MyBatis通過(guò)MappedStatement類描述Mapper的信息旅挤,包括命名空間踢关、ID(每條SQL命令都有唯一ID,在SqlSession中執(zhí)行哪條SQL命令由ID指定)粘茄、屬性類型签舞、返回值類型、SQL語(yǔ)句等柒瓣。
MappedMethod則封裝了Mapper接口的方法儒搭,invoke方法內(nèi)通過(guò)調(diào)用MapperMethod的execute執(zhí)行SQL。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 其中command為MapperMethod構(gòu)造是創(chuàng)建的SqlCommand對(duì)象
// 獲取SQL語(yǔ)句類型
switch (command.getType()) {
case INSERT: {
// 獲取參數(shù)信息
Object param = method.convertArgsToSqlCommandParam(args);
// 調(diào)用SqlSession的insert()方法芙贫,然后調(diào)用rowCountResult()方法統(tǒng)計(jì)行數(shù)
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
...
}
更加詳細(xì)的過(guò)程可以看看這篇文章搂鲫。
項(xiàng)目準(zhǔn)備
以下為我們可能會(huì)用到的類:
- MyConfiguration 封裝配置文件的配置信息
- MyEnvironment 封裝數(shù)據(jù)源信息,其實(shí)可以直接將數(shù)據(jù)源信息放MyConfiguration中
- MyMapperProxy Mapper接口的代理對(duì)象
- MyXMLParser XML文件解析
- MySqlSession 封裝與數(shù)據(jù)庫(kù)打交道方法的類
- MySqlSessionFactory 構(gòu)建MySqlSession
- MyExecutor 執(zhí)行具體的查詢磺平、處理結(jié)果集
大致步驟如下:
// 1.讀取mybatis-config.xml配置文件
// 2.構(gòu)建SqlSessionFactory
// 3.打開SqlSession
// 4.獲取Mapper接口對(duì)象
// 5.調(diào)用Mapper接口對(duì)象的方法操作數(shù)據(jù)庫(kù)
下面就正式開始吧魂仍。
新建Maven項(xiàng)目拐辽,導(dǎo)入JDBC依賴:
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
</dependencies>
數(shù)據(jù)庫(kù)準(zhǔn)備:
create database handwritten_mybatis;
create table handwritten_mybatis.h_user_info
(
id int auto_increment
primary key,
username varchar(20) null,
phone varchar(20) null
);
insert into h_user_info values (default, "Q", "18888888888");
UserInfo類:
public class UserInfo {
private Integer id;
private String username;
private String phone;
//Getter、Setter
}
UserInfoMapper映射接口:
public interface UserInfoMapper {
UserInfo selectByPrimaryKey(Integer id);
}
主測(cè)試類:
public class Application {
public static void main(String[] args) {
// 1.讀取mybatis-config.xml配置文件
// 2.構(gòu)建SqlSessionFactory
// 3.打開SqlSession
// 4.獲取Mapper接口對(duì)象
// 5.調(diào)用Mapper接口對(duì)象的方法操作數(shù)據(jù)庫(kù)
// 業(yè)務(wù)處理
}
}
MyBatis配置文件mybatis-config.xml擦酌,存放在resource目錄下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="autoCommit" value="true"/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<property name="url" value="jdbc:mysql://localhost:3306/handwritten_mybatis?userUnicode=true&serverTimezone=GMT%2B8"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserInfoMapper.xml" />
</mappers>
</configuration>
開始
讀取配置文件
InputStream inputStream = Application.class.getResourceAsStream("/mybatis-config.xml");
構(gòu)建SqlSessionFactory
先抽取我們的MyConfiguration存儲(chǔ)配置文件配置信息:
public class MyConfiguration {
// mybatis-config.xml
private MyEnvironment myEnvironment;
// xxMapper.xml
private Map<String ,MyMapperStatement> myMapperStatementMap;
//Getter俱诸、Setter
}
myMapperStatementMap用來(lái)存儲(chǔ)Mapper文件方法映射,關(guān)于MyMapperStatement:
public class MyMapperStatement {
private String namespace;
private String id;
private String parameterType;
private String resultType;
private String sql;
//Getter赊舶、Setter
}
用于封裝Mapper中的方法睁搭,namespace為類的全限定名,id則是方法名笼平,其他三個(gè)分別為參數(shù)類型园骆、返回值類型以及SQL語(yǔ)句。
MyEnvironment則是用來(lái)存儲(chǔ)數(shù)據(jù)源信息的:
public class MyEnvironment {
private String driver;
private String url;
private String username;
private String password;
//Getter寓调、Setter
}
對(duì)XML文件進(jìn)行讀取處理不是我們的目的锌唾,所以我這里直接給出工具類的代碼(我當(dāng)時(shí)弄了蠻久豪治,視頻中代碼一閃而過(guò)而且不全):
public class MyXMLConfigBuilder {
private MyXPathParser parser;
private boolean parsed = false;
public MyXMLConfigBuilder(InputStream in) {
this.parser = new MyXPathParser(in);
}
public MyConfiguration parse() {
if (parsed) {
throw new RuntimeException("Each XMLConfigBuilder can only be used once.");
}
//是否解析過(guò)xml配置文件的開關(guān)
parsed = true;
//parser 是一個(gè) XPath的解析器侍郭,evalNode 評(píng)估、評(píng)價(jià)锌介、計(jì)算 節(jié)點(diǎn)的值秋麸,得到一個(gè)封裝后的XNode對(duì)象
Node dataSourceNode = parser.evalNode("/configuration/environments/environment/dataSource");
NodeList childNodes = dataSourceNode.getChildNodes();
MyConfiguration configuration = new MyConfiguration();
MyEnvironment environment = new MyEnvironment();
for (int i = 0; i<childNodes.getLength();i++) {
Node item = childNodes.item(i);
if (item.getNodeType() == Node.ELEMENT_NODE){
String name = item.getAttributes().getNamedItem("name").getNodeValue();
String value = item.getAttributes().getNamedItem("value").getNodeValue();
if (name.equals("driver")){
environment.setDriver(value);
} else if (name.equals("url")){
environment.setUrl(value);
} else if (name.equals("username")){
environment.setUsername(value);
} else if (name.equals("password")){
environment.setPassword(value);
}
}
}
configuration.setMyEnvironment(environment);
//parser 是一個(gè) XPath的解析器渐排,evalNode 評(píng)估、評(píng)價(jià)灸蟆、計(jì)算 節(jié)點(diǎn)的值驯耻,得到一個(gè)封裝后的XNode對(duì)象
Node MapperNode = parser.evalNode("/configuration/mappers");
NodeList mapperNodes = MapperNode.getChildNodes();
Map<String, MyMapperStatement> mapperXmlMap = new HashMap<>();
for (int i = 0; i<mapperNodes.getLength();i++) {
Node item = mapperNodes.item(i);
if (item.getNodeType() == Node.ELEMENT_NODE){
String resource = item.getAttributes().getNamedItem("resource").getNodeValue();
InputStream mapperStream = this.getClass().getClassLoader().getResourceAsStream(resource);
this.parser = new MyXPathParser(mapperStream);
Node mapperNode = parser.evalNode("/mapper");
String namespace = mapperNode.getAttributes().getNamedItem("namespace").getNodeValue();
NodeList mapperChildNodes = mapperNode.getChildNodes();
for (int j = 0; j<mapperChildNodes.getLength();j++) {
Node itemj = mapperChildNodes.item(j);
if (itemj.getNodeType() == Node.ELEMENT_NODE){
String sqlId = itemj.getAttributes().getNamedItem("id").getNodeValue();
String parameterType = itemj.getAttributes().getNamedItem("parameterType").getNodeValue();
String resultType = null;
if (itemj.getAttributes().getNamedItem("resultType") != null){
resultType = itemj.getAttributes().getNamedItem("resultType").getNodeValue();
}
String sql = itemj.getTextContent();
// 操作類型(增刪改查)
// String sqlType = itemj.getNodeName();
MyMapperStatement mapperXml = new MyMapperStatement();
mapperXml.setId(sqlId);
mapperXml.setNamespace(namespace);
mapperXml.setParameterType(parameterType);
mapperXml.setResultType(resultType);
mapperXml.setSql(sql);
// mapperXml.setSqlType(sqlType);
mapperXmlMap.put(namespace + "." + sqlId,mapperXml);
}
}
}
}
configuration.setMyMapperStatementMap(mapperXmlMap);
//返回解析后的數(shù)據(jù)封裝對(duì)象configuration
return configuration;
}
}
public class MyXPathParser {
private Document document;
private boolean validation;
private XPath xpath;
public MyXPathParser(InputStream in) {
//初始化當(dāng)前類的四個(gè)成員變量:validation; entityResolver; variables; xpath;
commonConstructor(true);
//初始化當(dāng)前類的document對(duì)象,創(chuàng)建Document對(duì)象
this.document = createDocument(new InputSource(in));
}
/**
* 通用的構(gòu)造器炒考,初始化當(dāng)前類的幾個(gè)成員變量
*
* @param validation
*/
private void commonConstructor(boolean validation) {
this.validation = validation;
//XPath方式的xml解析對(duì)象
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
/**
* 評(píng)估可缚、評(píng)價(jià)、計(jì)算 節(jié)點(diǎn)的值
*
* @param expression
* @return
*/
public Node evalNode(String expression) {
//jdk的xml文件一個(gè)節(jié)點(diǎn)
Node node = (Node) evaluate(expression, document, XPathConstants.NODE);
if (node == null) {
return null;
}
//XNode是mybatis封裝的斋枢,代表xml節(jié)點(diǎn)的一個(gè)對(duì)象
return node;
}
/**
* 在一個(gè)指定的上下文文檔中 評(píng)估帘靡、評(píng)價(jià)、計(jì)算 一個(gè)XPath表達(dá)式的值瓤帚,并返回指定的類型
*
* @param expression
* @param root
* @param returnType
* @return
*/
private Object evaluate(String expression, Object root, QName returnType) {
try {
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new RuntimeException("Error evaluating XPath. Cause: " + e, e);
}
}
/**
* 創(chuàng)建Document對(duì)象
*
* @param inputSource
* @return
*/
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
//JDK提供的文檔解析工廠對(duì)象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//設(shè)置是否驗(yàn)證
factory.setValidating(validation);
//設(shè)置是否支持命名空間
factory.setNamespaceAware(false);
//設(shè)置是否忽略注釋
factory.setIgnoringComments(true);
//設(shè)置是否忽略元素內(nèi)容的空白
factory.setIgnoringElementContentWhitespace(false);
//是否將CDATA節(jié)點(diǎn)轉(zhuǎn)換為文本節(jié)點(diǎn)
factory.setCoalescing(false);
//設(shè)置是否展開實(shí)體引用節(jié)點(diǎn)描姚,這里是sql片段引用
factory.setExpandEntityReferences(true);
//創(chuàng)建一個(gè)DocumentBuilder對(duì)象
DocumentBuilder builder = factory.newDocumentBuilder();
//設(shè)置解析mybatis xml文檔節(jié)點(diǎn)的解析器,也就是上面的XMLMapperEntityResolver
// builder.setEntityResolver(entityResolver);
//設(shè)置解析文檔錯(cuò)誤的處理
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
}
});
//解析輸入源的xml數(shù)據(jù)為一個(gè)Document文件
return builder.parse(inputSource);
} catch (Exception e) {
throw new RuntimeException("Error creating document instance. Cause: " + e, e);
}
}
}
好的,我們需要達(dá)到的目的是獲取MySqlSessionFactory:
// 1.讀取mybatis-config.xml配置文件
InputStream inputStream = Application.class.getResourceAsStream("/mybatis-config.xml");
// 2.構(gòu)建SqlSessionFactory
MySqlSessionFactory mySqlSessionFactory = new MySqlSessionFactoryBuilder().build(inputStream);
很明顯MySqlSessionFacotryBuilder類用于構(gòu)建MySqlSessionFactory戈次,我們可以這樣:
public class MySqlSessionFactoryBuilder {
public MySqlSessionFactory build(InputStream inputStream) {
// MyBatis配置信息
MyConfiguration myConfiguration = new MyXMLConfigBuilder(inputStream).parse();
return new MySqlSessionFactory(myConfiguration);
}
}
也就是通過(guò)MyXMLConfigBuilder類解析輸入流并構(gòu)建MyConfiguration轩勘,之后再將MyConfiguration注入MySqlSessionFactory后返回。
構(gòu)建SqlSession
// 1.讀取mybatis-config.xml配置文件
InputStream inputStream = Application.class.getResourceAsStream("/mybatis-config.xml");
// 2.構(gòu)建SqlSessionFactory
MySqlSessionFactory mySqlSessionFactory = new MySqlSessionFactoryBuilder().build(inputStream);
// 3.打開SqlSession
MySqlSession mySqlSession = mySqlSessionFactory.openSession();
關(guān)于上文中的MySqlSessionFacotry:
public class MySqlSessionFactory {
private MyConfiguration myConfiguration;
public MySqlSessionFactory(MyConfiguration myConfiguration) {
this.myConfiguration = myConfiguration;
}
public MySqlSession openSession() {
MyExecutor myExecutor = new MyExecutor(myConfiguration);
return new MySqlSession(myConfiguration, myExecutor);
}
}
目的就是構(gòu)建MySqlSession怯邪。
而MySqlSession就是暴露給外部應(yīng)用使用的接口層:
public class MySqlSession {
private MyConfiguration myConfiguration;
private MyExecutor myExecutor;
public MySqlSession(MyConfiguration myConfiguration, MyExecutor myExecutor) {
this.myConfiguration = myConfiguration;
this.myExecutor = myExecutor;
}
}
MyExecutor則是真正與數(shù)據(jù)庫(kù)打交道的類:
public class MyExecutor {
private MyDataSource dataSource;
public MyExecutor(MyConfiguration myConfiguration) {
dataSource = MyDataSource.getInstance(myConfiguration.getMyEnvironment());
}
}
所以它需要有查詢方法:
public <T> List<T> query(MyMapperStatement mapperStatement, Object param) {
List<T> resultList = new ArrayList<>();
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = dataSource.getConnection();
String sql = mapperStatement.getSql();
String parse = SQLTokenParser.parse(sql);
preparedStatement = connection.prepareStatement(parse);
if (param instanceof Integer) {
preparedStatement.setInt(1, (Integer) param);
} else if (param instanceof Long) {
preparedStatement.setLong(1, (Long) param);
} else if (param instanceof Double) {
preparedStatement.setDouble(1, (Double) param);
} else if (param instanceof String) {
preparedStatement.setString(1, (String) param);
}
resultSet = preparedStatement.executeQuery();
// 處理查詢之后的結(jié)果
handleResultSet(resultSet, resultList, mapperStatement.getResultType());
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (null != connection) {
// 將連接歸還連接池
dataSource.release(connection);
}
}
return resultList;
}
private <T> void handleResultSet(ResultSet resultSet, List<T> resultList, String resultType) {
try {
Class<?> aClass = Class.forName(resultType);
while (resultSet.next()) {
T result = (T) aClass.getConstructor(null).newInstance();
// 把從數(shù)據(jù)庫(kù)查詢出來(lái)的結(jié)果集字段的數(shù)據(jù)要設(shè)置到result
ReflectUtil.setProToBeanFromResult(result, resultSet);
resultList.add(result);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}finally {
if (null != resultSet) {
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
關(guān)于ReflectUtil:
public class ReflectUtil {
public static void setProToBeanFromResult(Object entity, ResultSet resultSet) throws SQLException {
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
int count = resultSetMetaData.getColumnCount();
Field[] declaredFields = entity.getClass().getDeclaredFields();
for (int i = 0; i < count; i++) {
String columnName = resultSetMetaData.getColumnName(i + 1).replace("_", "").toUpperCase();
for (int i1 = 0; i1 < declaredFields.length; i1++) {
String fieldName = declaredFields[i1].getName().toUpperCase();
if (columnName.equalsIgnoreCase(fieldName)) {
if (declaredFields[i1].getType().getSimpleName().equals("Integer")) {
setProToBean(entity,declaredFields[i1].getName(),resultSet.getInt(resultSetMetaData.getColumnName(i + 1)));
} else if (declaredFields[i1].getType().getSimpleName().equals("Long")) {
setProToBean(entity,declaredFields[i1].getName(),resultSet.getLong(resultSetMetaData.getColumnName(i + 1)));
}else if (declaredFields[i1].getType().getSimpleName().equals("String")) {
setProToBean(entity,declaredFields[i1].getName(),resultSet.getString(resultSetMetaData.getColumnName(i + 1)));
}else if (declaredFields[i1].getType().getSimpleName().equals("Date")) {
setProToBean(entity,declaredFields[i1].getName(),resultSet.getDate(resultSetMetaData.getColumnName(i + 1)));
}else if (declaredFields[i1].getType().getSimpleName().equals("Boolean")) {
setProToBean(entity,declaredFields[i1].getName(),resultSet.getBoolean(resultSetMetaData.getColumnName(i + 1)));
}else if (declaredFields[i1].getType().getSimpleName().equals("BigDecimal")) {
setProToBean(entity,declaredFields[i1].getName(),resultSet.getBigDecimal(resultSetMetaData.getColumnName(i + 1)));
}
break;
}
}
}
}
private static void setProToBean(Object bean, String name, Object value) {
try {
Field field = bean.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(bean, value);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
獲取Mapper接口
// 1.讀取mybatis-config.xml配置文件
InputStream inputStream = Application.class.getResourceAsStream("/mybatis-config.xml");
// 2.構(gòu)建SqlSessionFactory
MySqlSessionFactory mySqlSessionFactory = new MySqlSessionFactoryBuilder().build(inputStream);
// 3.打開SqlSession
MySqlSession mySqlSession = mySqlSessionFactory.openSession();
// 4.獲取Mapper接口對(duì)象
UserInfoMapper userInfoMapper = mySqlSession.getMapper(UserInfoMapper.class);
所以我們還需要為我們的MySqlSession類添加一個(gè)getMapper方法绊寻,以獲取Mapper接口的代理:
public <T> T getMapper(Class<T> tClass) {
MyMapperProxy myMapperProxy = new MyMapperProxy(this);
return (T)Proxy.newProxyInstance(tClass.getClassLoader(),
// class com.sun.proxy.$Proxy0 cannot be cast to class top.hellooooo.mapper.UserInfoMapper
// tClass.getInterfaces(),
new Class[]{tClass},
myMapperProxy);
}
MyMapperProxy也就是代理類:
public class MyMapperProxy implements InvocationHandler {
private MySqlSession mySqlSession;
public MyMapperProxy(MySqlSession mySqlSession) {
this.mySqlSession = mySqlSession;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
Class<?> returnType = method.getReturnType();
// 如果是集合的子類
if (Collection.class.isAssignableFrom(returnType)) {
return mySqlSession.selectList(args);
} else if (Map.class.isAssignableFrom(returnType)) {
return mySqlSession.selectMap(args);
} else {
String statementKey = method.getDeclaringClass().getName() + "." + method.getName();
// 返回對(duì)象數(shù)據(jù)
return mySqlSession.selectOne(statementKey, args);
}
}
}
invoke方法里調(diào)用了mySqlSession的selectOne方法:
public <T> T selectOne(String statementKey, Object[] args) {
// key = namespace . selectId
MyMapperStatement mapperStatement = myConfiguration.getMyMapperStatementMap().get(statementKey);
List<T> query = myExecutor.query(mapperStatement, args != null ? args[0] : null);
if (query != null && query.size() > 1) {
throw new RuntimeException("Too many result..");
} else {
return query.get(0);
}
}
可以看到,最后也還是調(diào)用了MyExecutor的query方法,MyExecutor類中有兩個(gè)方法澄步,一個(gè)為query為查詢方法冰蘑,另一個(gè)則是處理返回結(jié)果的handleResultSet:
public <T> List<T> query(MyMapperStatement mapperStatement, Object param) {
List<T> resultList = new ArrayList<>();
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = dataSource.getConnection();
String sql = mapperStatement.getSql();
String parse = SQLTokenParser.parse(sql);
preparedStatement = connection.prepareStatement(parse);
if (param instanceof Integer) {
preparedStatement.setInt(1, (Integer) param);
} else if (param instanceof Long) {
preparedStatement.setLong(1, (Long) param);
} else if (param instanceof Double) {
preparedStatement.setDouble(1, (Double) param);
} else if (param instanceof String) {
preparedStatement.setString(1, (String) param);
}
resultSet = preparedStatement.executeQuery();
// 處理查詢之后的結(jié)果
handleResultSet(resultSet, resultList, mapperStatement.getResultType());
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (null != connection) {
// 將連接歸還連接池
dataSource.release(connection);
}
}
return resultList;
}
private <T> void handleResultSet(ResultSet resultSet, List<T> resultList, String resultType) {
try {
Class<?> aClass = Class.forName(resultType);
while (resultSet.next()) {
T result = (T) aClass.getConstructor(null).newInstance();
// 把從數(shù)據(jù)庫(kù)查詢出來(lái)的結(jié)果集字段的數(shù)據(jù)要設(shè)置到result
ReflectUtil.setProToBeanFromResult(result, resultSet);
resultList.add(result);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}finally {
if (null != resultSet) {
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
調(diào)用接口的方法
// 1.讀取mybatis-config.xml配置文件
InputStream inputStream = Application.class.getResourceAsStream("/mybatis-config.xml");
// 2.構(gòu)建SqlSessionFactory
MySqlSessionFactory mySqlSessionFactory = new MySqlSessionFactoryBuilder().build(inputStream);
// 3.打開SqlSession
MySqlSession mySqlSession = mySqlSessionFactory.openSession();
// 4.獲取Mapper接口對(duì)象
UserInfoMapper userInfoMapper = mySqlSession.getMapper(UserInfoMapper.class);
// 5.調(diào)用Mapper接口對(duì)象的方法操作數(shù)據(jù)庫(kù)
UserInfo userInfo = userInfoMapper.selectByPrimaryKey(1);
可以看到調(diào)用了selectByPrimaryKey,然而userInfoMapper是一個(gè)接口驮俗,很明顯沒(méi)法用懂缕,所以JVM會(huì)自動(dòng)調(diào)用代理對(duì)象的invoke方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
Class<?> returnType = method.getReturnType();
// 如果是集合的子類
if (Collection.class.isAssignableFrom(returnType)) {
return mySqlSession.selectList(args);
} else if (Map.class.isAssignableFrom(returnType)) {
return mySqlSession.selectMap(args);
} else {
String statementKey = method.getDeclaringClass().getName() + "." + method.getName();
// 返回對(duì)象數(shù)據(jù)
return mySqlSession.selectOne(statementKey, args);
}
}
測(cè)試:
public static void main(String[] args) {
// 1.讀取mybatis-config.xml配置文件
InputStream inputStream = Application.class.getResourceAsStream("/mybatis-config.xml");
// 2.構(gòu)建SqlSessionFactory
MySqlSessionFactory mySqlSessionFactory = new MySqlSessionFactoryBuilder().build(inputStream);
// 3.打開SqlSession
MySqlSession mySqlSession = mySqlSessionFactory.openSession();
// 4.獲取Mapper接口對(duì)象
UserInfoMapper userInfoMapper = mySqlSession.getMapper(UserInfoMapper.class);
// 5.調(diào)用Mapper接口對(duì)象的方法操作數(shù)據(jù)庫(kù)
UserInfo userInfo = userInfoMapper.selectByPrimaryKey(1);
// 業(yè)務(wù)處理
System.out.println(userInfo);
}
結(jié)果:
UserInfo{id=1, username='Q', phone='18888888888'}