mybatis源码刨析总结
拉勾 mybatis
初始化
1.創建git倉庫
1.新建一個目錄 然后點擊右鍵 git base here 創建git (會彈出一個窗口)
2.初始化 再窗口輸入 git init
3.指定倉庫 git clone 倉庫地址
4.上傳文件 點擊右鍵 git提交->master (選擇提交并推送)
1. jdbc連接數據庫的缺陷
1.1 代碼
public static void main(String[] args) {Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;try {//加載數據庫驅動Class.forName("com.mysql.jdbc.Driver");//通過驅動管理類獲取數據庫連接connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/lgstudy?characterEncoding=utf-8", "root", "root");//---有參數 // //?占位符 // String sql = "select * from user where username = ?"; // //獲取預處理statement // preparedStatement = connection.prepareStatement(sql); // // 設置參數 // preparedStatement.setString(1, "占山");//---無參數String sql = "select * from user";preparedStatement = connection.prepareStatement(sql);// 執行sql查詢結果集resultSet = preparedStatement.executeQuery();//遍歷結果集while (resultSet.next()) {int id = resultSet.getInt("id");String username = resultSet.getString("username");User user = new User();//封裝useruser.setId(id);user.setUsername(username);System.out.println(user);}} catch (Exception e) {e.printStackTrace();} finally{// 釋放資源if (resultSet != null) {try {resultSet.close();} catch (SQLException e) {e.printStackTrace();}}if (preparedStatement != null) {try {preparedStatement.close();} catch (SQLException e) {e.printStackTrace();}}if (connection != null) {try {connection.close();} catch (SQLException e) {e.printStackTrace();}}}}1.2 缺陷
1.連接數據庫的配置信息硬編碼 ===》存到配置文件中 2.數據庫的頻繁斷開連接==》連接池 3.sql語句,設置參數 硬編碼 ==》存到配置文件中 設置參數 反射 4.手動遍歷封裝結果集 ==》 內省2. 自定義持久層框架
2.1 思路
2.1.1使用端
1.引入自定義持久層的包 2.提供兩部分配置信息:1.數據庫的連接配置信息 sql配置信息2.mapper.xml 存放sql配置信息2.1.2自定義持久層框架(封裝jdbc的操作)
1.加載配置文件;根據配置文件的路徑,加載配置成字節輸入流,存儲在內存中 2.創建兩個javaBean(容器)Configuration核心配置類:存放sql的配置信息MappedStatement映射配置類 存放mapper.xml信息 3.解析配置文件 dom4j創建類sqlSessionFactoryBuilder1.使用dom4j解析配置文件,將解析出來的文件存放到javabean(容器)中2.創建sqlSessionFactory對象,生產session (工廠模式) 4.創建sqlSessionFactory接口實現類openSession 5.創建sqlSession接口及DefaultSession定義數據庫的crud操作 selectlist()update() 6.執行Exector接口及實現類SimpleExectorquery()//執行jdbc代碼2.1.3自定義持久層框架(缺陷)
1.dao層,存在代碼重復,整個操作的過程模版重復(加載配置文件) 2.statementId存在硬編碼 ========》jdk動態代理 Proxy.newProxyInstance InvocationHandler2.2 代碼實現
2.2.1 使用端
1.創建項目
1.創建lagou-mybatis項目(maven骨架項目)
2.創建sqlMapConfig.xml(sql源配置文件)
<configuration><!--數據庫配置信息--><dataSource><property name="driverClass" value="com.mysql.jdbc.Driver"></property><property name="jdbcUrl" value="jdbc:mysql:///lgstudy"></property><property name="username" value="root"></property><property name="password" value="root"></property></dataSource><!--存放mapper.xml的全路徑--><mapper resource="UserMapper.xml"></mapper> </configuration>#####2.創建UserMapper.xml(user類的sql)
<!--namespace 命名空間 當前xml的--><!--有三種全路徑:namespace綁定實體類的全路徑,綁定dao接口的全路徑,綁定mapper的sql.xml文件。第一種:namespace綁定實體類的全路徑:第二種:namespace綁定dao層接口的全路徑:第三種:namespace綁定的是mapper接口對應的sql.xml。https://www.cnblogs.com/zjdxr-up/p/8681382.html--> <mapper namespace="com.test.dao.IUserDao"><!--sql的唯一標識:namespace.id來組成 : statementId--><select id="findAll" resultType="com.test.pojo.User" >select * from user</select><select id="findOneById" resultType="com.test.pojo.User" paramterType="com.test.pojo.User">select * from user where id = #{id}</select></mapper>2.2.2 框架端
1.創建項目
1.創建lagou-mybatis項目(maven骨架項目)
2.pojo類
Configuration 用于存儲 數據庫源
public class Configuration {private DataSource dataSource;/** key: statementid value:封裝好的mappedStatement對象* */Map<String,MappedStatement> mappedStatementMap = new HashMap<>();}MappedStatement 用于存儲 sql文件的引用
//存儲的SQL xml文件的映射對象 public class MappedStatement {//id標識private String id;//返回值類型private String resultType;//參數值類型private String paramterType;//sql語句private String sql;}3.io類
Resources
public class Resources {// 根據配置文件的路徑,將配置文件加載成字節輸入流,存儲在內存中public static InputStream getResourceAsSteam(String path){InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);return resourceAsStream;} }4.掃描類 封裝 UserMapper
public class XMLMapperBuilder {private Configuration configuration;private String namespace;public XMLMapperBuilder(Configuration configuration) {this.configuration =configuration;}public void parse(InputStream inputStream) throws DocumentException {Document document = new SAXReader().read(inputStream);Element rootElement = document.getRootElement();//獲取定義的命名空間namespace = rootElement.attributeValue("namespace");getNodes(rootElement);}public void getNodes(Element rootElement){//獲取標簽名稱String name = rootElement.getName();List<Element> list = rootElement.selectNodes("//"+name);for (Element element : list) {String id = element.attributeValue("id");String resultType = element.attributeValue("resultType");String paramterType = element.attributeValue("paramterType");String sqlText = element.getTextTrim();if(id!=null){MappedStatement mappedStatement = new MappedStatement();mappedStatement.setId(id);mappedStatement.setResultType(resultType);mappedStatement.setParamterType(paramterType);mappedStatement.setSql(sqlText);mappedStatement.setTypeValue(name);//封裝keyString key = namespace+"."+id;configuration.getMappedStatementMap().put(key,mappedStatement);}}//遞歸遍歷當前節點所有的子節點List<Element> listElement=rootElement.elements();//所有一級子節點的listfor(Element e:listElement){//遍歷所有一級子節點this.getNodes(e);//遞歸}} }5.方法反射類
public class DefaultSqlSession implements SqlSession {private Configuration configuration;public DefaultSqlSession(Configuration configuration) {this.configuration = configuration;}@Overridepublic <E> List<E> selectList(String statementId, Object... param) throws Exception {//調用執行器//將要去完成對simpleExecutor里的query方法的調用simpleExecutor simpleExecutor = new simpleExecutor();//根據statementId獲取MappedMappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);List<Object> list = simpleExecutor.query(configuration, mappedStatement, param);return (List<E>) list;}@Overridepublic <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("查詢結果為空或者返回結果過多");}}@Overridepublic int update(String statementId, Object... params) throws Exception {simpleExecutor simpleExecutor = new simpleExecutor();MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);int update = simpleExecutor.update(configuration, mappedStatement, params);return update;}@Overridepublic <T> T getMapper(Class<?> mapperClass) {// 使用JDK動態代理來為Dao接口生成代理對象,并返回 // loader: 用哪個類加載器去加載代理對象 // interfaces:動態代理類需要實現的接口 // h:動態代理方法在執行時,會調用h里面的invoke方法去執行Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {//proxy 當前代理對象的應用//method 當前被調用方法的引用//args 傳遞的參數@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 底層都還是去執行JDBC代碼 //根據不同情況,來調用selctList或者selectOne// 準備參數 1:statmentid :sql語句的唯一標識:namespace.id= 接口全限定名.方法名// 方法名:findAllString methodName = method.getName();String className = method.getDeclaringClass().getName();Object result = new Object();String statementId = className + "." + methodName;//com.test.dao.IUserDao.updateById//com.test.dao.IUserDao.updateMappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);//獲取方法的標簽值String typeValue = mappedStatement.getTypeValue();switch (typeValue) {case "insert": {result = update(statementId, args);break;}case "update": {result = update(statementId, args);break;}case "delete": {result = update(statementId, args);break;}case "select": {Type genericReturnType = method.getGenericReturnType();// 判斷是否進行了 泛型類型參數化if (genericReturnType instanceof ParameterizedType) {List<Object> objects = selectList(statementId, args);result= objects;break;}else{result= selectOne(statementId, args);break;}}}return result;}});return (T) proxyInstance;}}6.JDbc執行類
public class simpleExecutor implements Executor {@Override //userpublic <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {// 1. 注冊驅動,獲取連接Connection connection = configuration.getDataSource().getConnection();// 2. 獲取sql語句 : select * from user where id = #{id} and username = #{username}//轉換sql語句: select * from user where id = ? and username = ? ,轉換的過程中,還需要對#{}里面的值進行解析存儲String sql = mappedStatement.getSql();BoundSql boundSql = getBoundSql(sql);// 3.獲取預處理對象:preparedStatementPreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());// 4. 設置參數//獲取到了 參數的全路徑String paramterType = mappedStatement.getParamterType();Class<?> paramtertypeClass = getClassType(paramterType);List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();for (int i = 0; i < parameterMappingList.size(); i++) {ParameterMapping parameterMapping = parameterMappingList.get(i);String content = parameterMapping.getContent();//反射Field declaredField = paramtertypeClass.getDeclaredField(content);//暴力訪問declaredField.setAccessible(true);Object o = declaredField.get(params[0]);preparedStatement.setObject(i+1,o);}// 5. 執行sqlResultSet resultSet = preparedStatement.executeQuery();//獲取返回值類String resultType = mappedStatement.getResultType();Class<?> resultTypeClass = getClassType(resultType);ArrayList<Object> objects = new ArrayList<>();// 6. 封裝返回結果集while (resultSet.next()){//反射new一個類Object o =resultTypeClass.newInstance();//元數據ResultSetMetaData metaData = resultSet.getMetaData();for (int i = 1; i <= metaData.getColumnCount(); 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);}objects.add(o);}return (List<E>) objects;}@Overridepublic int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {//update user set username=#{} where id=#{}// 1. 注冊驅動,獲取連接Connection connection = configuration.getDataSource().getConnection();// 2. 獲取sql語句 : select * from user where id = #{id} and username = #{username}//轉換sql語句: select * from user where id = ? and username = ? ,轉換的過程中,還需要對#{}里面的值進行解析存儲String sql = mappedStatement.getSql();BoundSql boundSql = getBoundSql(sql);// 3.獲取預處理對象:preparedStatementPreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());// 4. 設置參數//獲取到了 參數的全路徑String paramterType = mappedStatement.getParamterType();Class<?> paramtertypeClass = getClassType(paramterType);List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();for (int i = 0; i < parameterMappingList.size(); i++) {ParameterMapping parameterMapping = parameterMappingList.get(i);String content = parameterMapping.getContent();Object o =null;if(mappedStatement.getTypeValue().equals("delete")){o= params[i];}else{//反射Field declaredField = paramtertypeClass.getDeclaredField(content);//暴力訪問declaredField.setAccessible(true);o = declaredField.get(params[0]);}preparedStatement.setObject(i+1,o);}// 5. 執行sqlint i1 = preparedStatement.executeUpdate();return i1;}private Class<?> getClassType(String paramterType) throws ClassNotFoundException {if(paramterType!=null){Class<?> aClass = Class.forName(paramterType);return aClass;}return null;}/*** 完成對#{}的解析工作:1.將#{}使用?進行代替,2.解析出#{}里面的值進行存儲* @param sql* @return*/private BoundSql getBoundSql(String sql) {//標記處理類:配置標記解析器來完成對占位符的解析處理工作ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);//解析出來的sqlString parseSql = genericTokenParser.parse(sql);//#{}里面解析出來的參數名稱List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();BoundSql boundSql = new BoundSql(parseSql,parameterMappings);return boundSql;} }7.測試類
@Test public void testProxyDao() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);User user = userMapper.findById(1);System.out.println(user);sqlSession.close(); }2.2.3打jar問題
1.在pom文件中添加編譯屬性
<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.encoding>UTF-8</maven.compiler.encoding><java.version>1.8</java.version><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties>2.idea中點擊 projrct Structure =>點擊module 設置項目的編譯版本
3.srtting=> builder=>compiler=>java compiler設置編譯版本
3.mybatis快速啟動
3.1導依賴
<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.encoding>UTF-8</maven.compiler.encoding><java.version>1.8</java.version><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><!--引入依賴--><dependencies><!--mybatis坐標--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.5</version></dependency><!--mysql驅動坐標--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.6</version><scope>runtime</scope></dependency><!--單元測試坐標--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency></dependencies>4 自定義插件
xml
<plugins><plugin interceptor="com.lagou.plugin.MyPlugin"><property name="name" value="Bob"/></plugin></plugins>java
package com.lagou.plugin;import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.plugin.*;import java.sql.Connection; import java.util.Properties;@Intercepts({@Signature(type= StatementHandler.class,//攔截那個接口method = "prepare",//這個接口內的那個方法名args={Connection.class,Integer.class})//攔截方法的入參,如果方法重載,通過方法名跟參數確定唯一})public class MyPlugin implements Interceptor {//這里是每次執行操作的時候,都會進行這個方法@Overridepublic Object intercept(Invocation invocation) throws Throwable {System.out.println("增強了");return invocation.proceed();}//把這個攔截器生成一個代理放到攔截器鏈中@Overridepublic Object plugin(Object target) {System.out.println("將要包裝的目標對象"+target);return Plugin.wrap(target,this);}//插件初始化時候調用,只調用一次 獲取配置文件的屬性@Overridepublic void setProperties(Properties properties) {System.out.println("獲取配置文件的屬性"+properties);}}mybatis 攔截的類
一、executor,executor類可以說是執行sql的全過程,如組裝參數,sql改造,結果處理,比較廣泛,但實際用的不多
二、StatementHandler,這個是執行sql的過程,可以獲取到待執行的sql,可用來改造sql,如分頁,分表,最常攔截的類
三、paremeterHandler,這個用來攔截sql的參數,可以自定義參數組裝規則
四、resultHandler,這個用來處理結果
5 緩存
一級緩存
二級緩存
二級緩存是序列化存儲 需要實現POJO需要實現Serializable接口
<settings><setting name="cacheEnabled" value="true" /> </settings> //在sql的mapper的添加 <cache/>表填問題
1.緩存使用順序
先到二級緩存當中查找 如果二級緩存中沒有,就去找一級緩存 如果一級緩存中也沒有就去到數據庫當中查詢2.二級Chache使用原則
1.只能在一個命名空間下使用二級緩存
由于二級緩存中的數據是基于namespace的,即不同namespace中的數據互不干擾。在多個namespace中若均存在對同一個表的操作,那么這多個namespace中的數據可能就會出現不一致現象。
2.在單表上使用二級緩存
如果一個表與其它表有關聯關系,那么久非常有可能存在多個namespace對同一數據的操作。而不同namespace中的數據互補干擾,所以就有可能出現多個namespace中的數據不一致現象。
3.查詢多于修改時使用二級緩存
在查詢操作遠遠多于增刪改操作的情況下可以使用二級緩存。因為任何增刪改操作都將刷新二級緩存,對二級緩存的頻繁刷新將降低系統性能。
java補充
1.java類型 T,E,K,V,?
泛型中的通配符?表示不確定的 java 類型 ?無界通配符 例子 List<? extends Animal> listAnimalsT (type) 表示具體的一個java類型List<T>是確定的某一個類型K V (key value) 分別代表java鍵值中的Key ValueE (element) 代表Element 上界通配符 < ? extends E> 下界通配符 < ? super E>T和?運用的地方有點不同,? 是定義在引用變量上T 是類上或方法上2. List<T.?.Object>區別
ArrayList<T> arrayList = new ArrayList<T>(); 指定集合元素類型只可以是T類型 ArrayList<?>arrayList = new ArrayList<?>(); 集合元素可以是任意類型,一般是方法中為了說明方法 ArrayList<? extends E> arrayList = new ArrayList<? extends E>();泛型的限定:? extends E:接受E類型或者E的子類型、? super E :接受E類型或者E的父類型3.java的內省與反射
//https://www.cnblogs.com/winclpt/articles/7405271.html1.內省是先得到屬性描述器PropertyDecriptor后再進行各種操作,內省(Introspector)是Java語言對JavaBean類屬性、事件的處理方法 2.反射則是先得到類的字節碼Class后再進行各種操作的。對任意一個類,能夠獲取得到這個類的所有屬性和方法; 代碼:反射:Field f = user.getClass().getDeclaredField("name");f.setAccessible(true);f.set(user, "mld");//設置屬性值內省//操作單個屬性PropertyDescriptor pd = new PropertyDescriptor("name", User.class);Method w = pd.getWriteMethod();//獲取屬性的setter方法w.invoke(user, "winclpt");4.java的代理
1.靜態代理
靜態代理:由程序員創建或特定工具自動生成源代碼,也就是在編譯時就已經將接口,被代理類,代理類等確定下來。在程序運行之前,代理類的.class文件就已經生成2.動態代理
實現InvocationHandler接口
動態代理就是要生成一個包裝類對象,由于代理的對象是動態的,所以叫動態代理。由于我們需要增強,這個增強是需要留給開發人員開發代碼的,因此代理類不能直接包含被代理對象,而是一個InvocationHandler,該InvocationHandler包含被代理對象,并負責分發請求給被代理對象,分發前后均可以做增強。從原理可以看出,JDK動態代理是“對象”的代理。
原文鏈接:https://blog.csdn.net/flyfeifei66/article/details/81481222 **
5.newInstance與new的區別
new關鍵字能調用任何構造方法。
newInstance()只能調用無參構造方法。
newInstance()構造對象的地方通過new關鍵字也可以創建對象.
在使用newInstance()方法的時候,必須保證這個類已經加載并且已經連接
適用:
使用newInstance()在通用性方面比較高,className我們可以用配置文件進行相關的配置。
String className = 從配置文件中讀取className;
A a = (A) Class.forName(className).newInstance();
再配合依賴注入的方法,就提高了軟件的可伸縮性、可擴展性。框架的開發中用的比較多!
單詞
statement 聲明,陳述
bound 一定會,邊界
Declaring 公然地,生命
Accessible 可進入的
descrip 描述
relation 關系
example例子
Generic通用的
Security 安全的
Multiple 多樣的
extract提取
wrap包
signature簽名
Additional附加的 額外的/
作業
簡答題
一.
1.Mybatis動態sql是做什么的?
1.動態sql就是 根據條件標簽動態的拼接sql,包括判空,循環,拼接等
2.哪些動態sql?
動態sql大致有
1.: if是為了判斷傳入的值是否符合某種規則,比如是否不為空;
2.:where標簽可以用來做動態拼接查詢條件,當和if標簽配合的時候,不用顯示的聲明類似where 1=1這種無用的條件
3.:foreach標簽可以把傳入的集合對象進行遍歷,然后把每一項的內容作為參數傳到sql語句中,
4.:include可以把大量重復的代碼整理起來,當使用的時候直接include即可,減少重復代碼的編寫;
5.:是一個格式化標簽
6.choose、when、otherwise 標簽類似于 Java 中的 switch、case、default。只有一個條件生效,
3. 簡述一下動態sql的執行原理?
1.在xmlMapperBuilder中 解析配置文件時
2.解析 <mapper /> 節點
3.解析 節點
// 1.在xmlMapperBuilder中 解析配置文件時public void parse() {// 解析 `<mapper />` 節點configurationElement(parser.evalNode("/mapper"));}//2. 解析 `<mapper />` 節點private void configurationElement(XNode context) {try {// 獲得 namespace 屬性String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");}// 設置 namespace 屬性builderAssistant.setCurrentNamespace(namespace);// 解析 <resultMap /> 節點們resultMapElements(context.evalNodes("/mapper/resultMap"));// 解析 <sql /> 節點們sqlElement(context.evalNodes("/mapper/sql"));// 解析 <select /> <insert /> <update /> <delete /> 節點們buildStatementFromContext(context.evalNodes("select|insert|update|delete"));} catch (Exception e) {throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);}}// 3.解析 <select /> <insert /> <update /> <delete /> 節點們private void buildStatementFromContext(List<XNode> list) {if (configuration.getDatabaseId() != null) {buildStatementFromContext(list, configuration.getDatabaseId());}buildStatementFromContext(list, null);// 上面兩塊代碼,可以簡寫成 buildStatementFromContext(list, configuration.getDatabaseId());}private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {//遍歷 <select /> <insert /> <update /> <delete /> 節點們for (XNode context : list) {// 創建 XMLStatementBuilder 對象,執行解析final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);try {statementParser.parseStatementNode();} catch (IncompleteElementException e) {// 解析失敗,添加到 configuration 中configuration.addIncompleteStatement(statementParser);}}}4.XMLStatementBuilder對象
public class XMLStatementBuilder extends BaseBuilder {/*** 執行解析*/public void parseStatementNode() {// 獲得 id 屬性,編號。String id = context.getStringAttribute("id");// 獲得 databaseId , 判斷 databaseId 是否匹配String databaseId = context.getStringAttribute("databaseId");if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}// 獲得各種屬性Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");String parameterMap = context.getStringAttribute("parameterMap");String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);String resultMap = context.getStringAttribute("resultMap");String resultType = context.getStringAttribute("resultType");String lang = context.getStringAttribute("lang");// 獲得 lang 對應的 LanguageDriver 對象LanguageDriver langDriver = getLanguageDriver(lang);// 獲得 resultType 對應的類Class<?> resultTypeClass = resolveClass(resultType);// 獲得 resultSet 對應的枚舉值String resultSetType = context.getStringAttribute("resultSetType");ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);// 獲得 statementType 對應的枚舉值StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));}5.LanguageDriver的實現類XMLLanguageDriver的方法解析方法createSqlSource
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {// 創建 XMLScriptBuilder 對象,執行解析XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);return builder.parseScriptNode();}6.解析
XMLScriptBuilder標簽解析 /*** XML 動態語句( SQL )構建器,負責將 SQL 解析成 SqlSource 對象** @author Clinton Begin*/ public class XMLScriptBuilder extends BaseBuilder {/*** 當前 SQL 的 XNode 對象*/private final XNode context;/*** 是否為動態 SQL*/private boolean isDynamic;/*** SQL 方法類型*/private final Class<?> parameterType;/*** NodeNodeHandler 的映射*/private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();public XMLScriptBuilder(Configuration configuration, XNode context) {this(configuration, context, null);}public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {super(configuration);this.context = context;this.parameterType = parameterType;// 初始化 nodeHandlerMap 屬性initNodeHandlerMap();}/*** 初始化 {@link #nodeHandlerMap} 屬性*/private void initNodeHandlerMap() {nodeHandlerMap.put("trim", new TrimHandler());nodeHandlerMap.put("where", new WhereHandler());nodeHandlerMap.put("set", new SetHandler());nodeHandlerMap.put("foreach", new ForEachHandler());nodeHandlerMap.put("if", new IfHandler());nodeHandlerMap.put("choose", new ChooseHandler());nodeHandlerMap.put("when", new IfHandler());nodeHandlerMap.put("otherwise", new OtherwiseHandler());nodeHandlerMap.put("bind", new BindHandler());}/*** 負責將 SQL 解析成 SqlSource 對象** @return SqlSource 對象*/public SqlSource parseScriptNode() {// 解析 SQLMixedSqlNode rootSqlNode = parseDynamicTags(context);// 創建 SqlSource 對象SqlSource sqlSource;if (isDynamic) {sqlSource = new DynamicSqlSource(configuration, rootSqlNode);} else {sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);}return sqlSource;}/*** 解析 SQL 成 MixedSqlNode 對象** @param node XNode 節點* @return MixedSqlNode*/protected MixedSqlNode parseDynamicTags(XNode node) {// 創建 SqlNode 數組List<SqlNode> contents = new ArrayList<>();// 遍歷 SQL 節點的所有子節點NodeList children = node.getNode().getChildNodes();for (int i = 0; i < children.getLength(); i++) {// 當前子節點XNode child = node.newXNode(children.item(i));// 如果類型是 Node.CDATA_SECTION_NODE 或者 Node.TEXT_NODE 時if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {// 獲得內容String data = child.getStringBody("");// 創建 TextSqlNode 對象TextSqlNode textSqlNode = new TextSqlNode(data);// 如果是動態的 TextSqlNode 對象if (textSqlNode.isDynamic()) {// 添加到 contents 中contents.add(textSqlNode);// 標記為動態 SQLisDynamic = true;// 如果是非動態的 TextSqlNode 對象} else {// 創建 StaticTextSqlNode 添加到 contents 中contents.add(new StaticTextSqlNode(data));}// 如果類型是 Node.ELEMENT_NODE} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628// 根據子節點的標簽,獲得對應的 NodeHandler 對象String nodeName = child.getNode().getNodeName();NodeHandler handler = nodeHandlerMap.get(nodeName);if (handler == null) { // 獲得不到,說明是未知的標簽,拋出 BuilderException 異常throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");}// 執行 NodeHandler 處理handler.handleNode(child, contents);// 標記為動態 SQLisDynamic = true;}}// 創建 MixedSqlNode 對象return new MixedSqlNode(contents);}/*** Node 處理器接口*/private interface NodeHandler {/*** 處理 Node** @param nodeToHandle 要處理的 XNode 節點* @param targetContents 目標的 SqlNode 數組。實際上,被處理的 XNode 節點會創建成對應的 SqlNode 對象,添加到 targetContents 中*/void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);}/*** `<bind />` 標簽的處理器** @see VarDeclSqlNode*/private class BindHandler implements NodeHandler {public BindHandler() {// Prevent Synthetic Access}@Overridepublic void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {// 解析 name、value 屬性final String name = nodeToHandle.getStringAttribute("name");final String expression = nodeToHandle.getStringAttribute("value");// 創建 VarDeclSqlNode 對象final VarDeclSqlNode node = new VarDeclSqlNode(name, expression);// 添加到 targetContents 中targetContents.add(node);}}/*** `<trim />` 標簽的處理器** @see TrimSqlNode*/private class TrimHandler implements NodeHandler {public TrimHandler() {// Prevent Synthetic Access}@Overridepublic void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {// 解析內部的 SQL 節點,成 MixedSqlNode 對象MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);// 獲得 prefix、prefixOverrides、"suffix"、suffixOverrides 屬性String prefix = nodeToHandle.getStringAttribute("prefix");String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");String suffix = nodeToHandle.getStringAttribute("suffix");String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");// 創建 TrimSqlNode 對象TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);// 添加到 targetContents 中targetContents.add(trim);}}/*** `<where />` 標簽的處理器** @see WhereSqlNode*/private class WhereHandler implements NodeHandler {public WhereHandler() {// Prevent Synthetic Access}@Overridepublic void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {// 解析內部的 SQL 節點,成 MixedSqlNode 對象MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);// 創建 WhereSqlNode 對象WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);// 添加到 targetContents 中targetContents.add(where);}}/*** `<set />` 標簽的處理器** @see SetSqlNode*/private class SetHandler implements NodeHandler {public SetHandler() {// Prevent Synthetic Access}@Overridepublic void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {// 解析內部的 SQL 節點,成 MixedSqlNode 對象MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);// 創建 SetSqlNode 對象SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);// 添加到 targetContents 中targetContents.add(set);}}/*** `<foreach />` 標簽的處理器** @see ForEachSqlNode*/private class ForEachHandler implements NodeHandler {public ForEachHandler() {// Prevent Synthetic Access}@Overridepublic void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {// 解析內部的 SQL 節點,成 MixedSqlNode 對象MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);// 獲得 collection、item、index、open、close、separator 屬性String collection = nodeToHandle.getStringAttribute("collection");String item = nodeToHandle.getStringAttribute("item");String index = nodeToHandle.getStringAttribute("index");String open = nodeToHandle.getStringAttribute("open");String close = nodeToHandle.getStringAttribute("close");String separator = nodeToHandle.getStringAttribute("separator");// 創建 ForEachSqlNode 對象ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator);// 添加到 targetContents 中targetContents.add(forEachSqlNode);}}/*** `<if />` 標簽的處理器** @see IfSqlNode*/private class IfHandler implements NodeHandler {public IfHandler() {// Prevent Synthetic Access}@Overridepublic void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {// 解析內部的 SQL 節點,成 MixedSqlNode 對象MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);// 獲得 test 屬性String test = nodeToHandle.getStringAttribute("test");// 創建 IfSqlNode 對象IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);// 添加到 targetContents 中targetContents.add(ifSqlNode);}}/*** `<otherwise />` 標簽的處理器*/private class OtherwiseHandler implements NodeHandler {public OtherwiseHandler() {// Prevent Synthetic Access}@Overridepublic void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {// 解析內部的 SQL 節點,成 MixedSqlNode 對象MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);// 添加到 targetContents 中targetContents.add(mixedSqlNode);}}/*** `<choose />` 標簽的處理器** @see ChooseSqlNode*/private class ChooseHandler implements NodeHandler {public ChooseHandler() {// Prevent Synthetic Access}@Overridepublic void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {List<SqlNode> whenSqlNodes = new ArrayList<>();List<SqlNode> otherwiseSqlNodes = new ArrayList<>();// 解析 `<when />` 和 `<otherwise />` 的節點們handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);// 獲得 `<otherwise />` 的節點SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);// 創建 ChooseSqlNode 對象ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);// 添加到 targetContents 中targetContents.add(chooseSqlNode);}private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes, List<SqlNode> defaultSqlNodes) {List<XNode> children = chooseSqlNode.getChildren();for (XNode child : children) {String nodeName = child.getNode().getNodeName();NodeHandler handler = nodeHandlerMap.get(nodeName);if (handler instanceof IfHandler) { // 處理 `<when />` 標簽的情況handler.handleNode(child, ifSqlNodes);} else if (handler instanceof OtherwiseHandler) { // 處理 `<otherwise />` 標簽的情況handler.handleNode(child, defaultSqlNodes);}}}// 至多允許有一個 SqlNode 節點private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {SqlNode defaultSqlNode = null;if (defaultSqlNodes.size() == 1) {defaultSqlNode = defaultSqlNodes.get(0);} else if (defaultSqlNodes.size() > 1) {throw new BuilderException("Too many default (otherwise) elements in choose statement.");}return defaultSqlNode;}}}二、
Mybatis是否支持延遲加載?如果支持,它的實現原理是什么?
延遲加載主要通過動態代理實現,通過代理攔截指定方法沒執行數據加載。
javassisProxyFactory會創建一個User代理對象,所有調用User對象方法,都會經過EnhancedResultObjectProxyImpl.invoke()方法的攔截。、
于是當調用User.getOrder()方法時,才真正去執行查詢Order的動作并把結果賦值
<settings><!-- 開啟全局配置的懶加載 --><setting name="lazyLoadingEnabled" value="true"/><!-- 關閉積極加載 --><setting name="aggressiveLazyLoading" value="false"/> </settings> <mapper namespace="demo.cyj.dao.TeacherDao"><select id="findTeacherByTname" resultType="Teacher" resultMap="getTeacher">select t.t_id t_id,t.t_name t_name from teacher t where t.t_name =#{name}</select>//延遲加載<resultMap type="Teacher" id="getTeacher"><collection ofType="Student" property="stuList" fetchType="lazy" column="t_name" select="findTeacherByTnameLazy" /></resultMap><select id="findTeacherByTnameLazy" resultType="Student" >select s.id id,s.stu_name stu_name,s.stu_age age,s.t_id t_id from student s left join teacher t on t.t_id = s.t_id where t.t_name=#{name} </select> </mapper>2.延遲加載的類
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());if (closed) {throw new ExecutorException("Executor was closed.");}if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();}List<E> list;try {queryStack++;list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {queryStack--;}if (queryStack == 0) {for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load();}// issue #601deferredLoads.clear();if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// issue #482clearLocalCache();}}return list;}延遲加載的類
private static class DeferredLoad {private final MetaObject resultObject;private final String property;private final Class<?> targetType;private final CacheKey key;private final PerpetualCache localCache;private final ObjectFactory objectFactory;private final ResultExtractor resultExtractor;// issue #781public DeferredLoad(MetaObject resultObject,String property,CacheKey key,PerpetualCache localCache,Configuration configuration,Class<?> targetType) {this.resultObject = resultObject;this.property = property;this.key = key;this.localCache = localCache;this.objectFactory = configuration.getObjectFactory();this.resultExtractor = new ResultExtractor(configuration, objectFactory);this.targetType = targetType;}public boolean canLoad() {return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;}public void load() {@SuppressWarnings( "unchecked" )// we suppose we get back a ListList<Object> list = (List<Object>) localCache.getObject(key);Object value = resultExtractor.extractObjectFromList(list, targetType);resultObject.setValue(property, value);}}三
1.Mybatis都有哪些Executor執行器
BaseExecutor 簡單執行器,是 MyBatis 中默認使用的執行器,每執行一次 update 或 select,就開啟一個 Statement 對象,用完就直接關閉 Statement 對象(可以是 Statement 或者是 PreparedStatment 對象)
BatchExecutor 批處理執行器,用于將多個SQL一次性輸出到數據庫
simpleExexutor 每執行一次update或select,就開啟一個Statement對象,用完立刻關閉Statement對象。[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-LX6M9gvA-1597914279840)(C:\Users\gaoyuan\Desktop\simpleExexutor.png)]
ReuseExecutor 執行update或select,以sql作為key查找Statement對象,存在就使用,不存在就創建,用完后,不關閉Statement對象,而是放置于Map內,供下一次使用。簡言之,就是重復使用Statement對象。
cachingExecutor 更新緩存
2.它們之間的區別是什么?
作用范圍:Executor的這些特點,都嚴格限制在SqlSession生命周期范圍內。
默認是SimplExcutor,需要配置在創建SqlSession對象的時候指定執行器的類型即可。
四
1.一級緩存
Mybatis的一級緩存是指SqlSession級別的,作用域是SqlSession,Mybatis默認開啟一級緩存,在同一個SqlSession中,相同的Sql查詢的時候,第一次查詢的時候,就會從緩存中取,如果發現沒有數據,那么就從數據庫查詢出來,并且緩存到HashMap中,如果下次還是相同的查詢,就直接從緩存中查詢,就不在去查詢數據庫,對應的就不在去執行SQL語句。當查詢到的數據,進行增刪改的操作的時候,緩存將會失效
2. 二級緩存
MyBatis的二級緩存是基于Mapper級別的,也就是說多個SqlSession去使用某個Mapper的查詢語句時,得到的緩存數據是可共用的。第一次調用mapper下的sql 的時候去查詢信息,查詢到的信息會存放到該mapper對應的二級緩存區域,第二次調用namespace下的mapper映射文件中,相同的SQL去查詢,回去對應的二級緩存內取結果。二級緩存開啟后,查詢就會走二級緩存,沒查到直接查庫。MyBatis默認不開啟二級緩存
五
簡述Mybatis的插件運行原理,以及如何編寫一個插件?
插件運行原理
實現Mybatis的Interceptor接口并復寫intercept()方法,然后在給插件編寫注解,指定要攔截哪一個接口的哪些方法即可
實現Interceptor接口 在定義StatementHandler處理器的時候攔截prepare方法也就是準備的方法
//1.configuration.newStatementHandler()獲取對象public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {Statement stmt = null;Configuration configuration = ms.getConfiguration();//定義StatementHandler處理器StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);//初始化stmt = prepareStatement(handler, ms.getStatementLog());//執行return handler.update(stmt);}//2.獲取執行sql的StatementHandler組件public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);//代理對象statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}//3.public class InterceptorChain {private final List<Interceptor> interceptors = new ArrayList<Interceptor>();public Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);}return target;}}如何編寫插件
實現Interceptor接口,Interceptors注解表明要攔截的類,方法,參數
package com.lagou.plugin;import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.plugin.*;import java.sql.Connection; import java.util.Properties;@Intercepts({@Signature(type= StatementHandler.class,//攔截那個接口method = "prepare",//這個接口內的那個方法名args={Connection.class,Integer.class})//攔截方法的入參,如果方法重載,通過方法名跟參數確定唯一})public class MyPlugin implements Interceptor {//這里是每次執行操作的時候,都會進行這個方法@Overridepublic Object intercept(Invocation invocation) throws Throwable {System.out.println("增強了");return invocation.proceed();}//把這個攔截器生成一個代理放到攔截器鏈中@Overridepublic Object plugin(Object target) {System.out.println("將要包裝的目標對象"+target);return Plugin.wrap(target,this);}//插件初始化時候調用,只調用一次 獲取配置文件的屬性@Overridepublic void setProperties(Properties properties) {System.out.println("獲取配置文件的屬性"+properties);}}tementHandler);return statementHandler;}//3.public class InterceptorChain {private final List<Interceptor> interceptors = new ArrayList<Interceptor>();public Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);}return target;}}如何編寫插件
實現Interceptor接口,Interceptors注解表明要攔截的類,方法,參數
package com.lagou.plugin;import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.plugin.*;import java.sql.Connection; import java.util.Properties;@Intercepts({@Signature(type= StatementHandler.class,//攔截那個接口method = "prepare",//這個接口內的那個方法名args={Connection.class,Integer.class})//攔截方法的入參,如果方法重載,通過方法名跟參數確定唯一})public class MyPlugin implements Interceptor {//這里是每次執行操作的時候,都會進行這個方法@Overridepublic Object intercept(Invocation invocation) throws Throwable {System.out.println("增強了");return invocation.proceed();}//把這個攔截器生成一個代理放到攔截器鏈中@Overridepublic Object plugin(Object target) {System.out.println("將要包裝的目標對象"+target);return Plugin.wrap(target,this);}//插件初始化時候調用,只調用一次 獲取配置文件的屬性@Overridepublic void setProperties(Properties properties) {System.out.println("獲取配置文件的屬性"+properties);}}總結
以上是生活随笔為你收集整理的mybatis源码刨析总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: spring源码刨析总结
- 下一篇: fullcalendar5.X版本 显示