一、Mybatis環境快速入門
1、maven依賴
<dependencies>
<!-- mybatis核心包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.0</version>
</dependency>
<!-- mysql驅動包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.29</version>
</dependency>
<!-- junit測試包 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
2、創建mybatis配置文件 configuration
<?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"/>
<!-- 數據庫連接相關配置 ,這里動態獲取config.properties文件中的內容-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- mapping文件路徑配置 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
3、Mapper配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 為這個mapper指定一個唯一的namespace,namespace的值習慣上設置成包名+sql映射文件名,這樣就能夠保證namespace的值是唯一的 例如namespace="com.mayikt.mapper.UserMapper"就是com.mayikt.mapper(包名)+userMapper(userMapper.xml文件去除后綴) -->
<mapper namespace="com.mayikt.mapper.UserMapper">
<!-- 在select標簽中編寫查詢的SQL語句, 設置select標簽的id屬性為getUser,id屬性值必須是唯一的,不能夠重復 使用parameterType屬性指明查詢時使用的參數類型,resultType屬性指明查詢返回的結果集類型 resultType="com.mayikt.entity.User"就表示將查詢結果封裝成一個User類的對象返回 User類就是users表所對應的實體類 -->
<!-- 根據id查詢得到一個user對象 -->
<select id="getUser" parameterType="int" resultType="com.mayikt.entity.UserEntity"> select * from user where id=#{id} </select>
</mapper>
4、實體類
public class UserEntity { private Integer id; private Date birdate; private String name; }
5、mapper接口
public interface UserMapper { public UserEntity getUser(int id); }
6、運行Mybatis代碼
public class TestMyBatis { public static void main(String[] args) { try { //配置文件
String configXml = "mybatis_config.xml"; //加載配置獲取流
Reader reader = Resources.getResourceAsReader(configXml); //獲取sqlSessionFactory工廠
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); //獲取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(); //獲取對應的mapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //執行方法
UserEntity user = userMapper.getUser(1); System.out.println("name:" + user.getName()); } catch (IOException e) { e.printStackTrace(); } } }
7、數據表結構
CREATE TABLE `user` ( `id` int(11) NOT NULL, `name` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
二、Mybatis核心配置文件
1、Properties(屬性)
Java屬性文件可以配置直觀的。
如:
<properties>
<property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="jdbc.url" value="jdbc:mysql:///mybatis"/>
<property name="jdbc.username" value="root"/>
<property name="jdbc.password" value="root"/>
</properties>
或者通過直接引入屬性文件,例如:
<properties resource="db.properties"></properties>
然后db.properties文件中的配置就是:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql:///mybatis
jdbc.username=root jdbc.password=root
2、typeAliases(類型別名)
類型別名是Java類型的簡稱。
逐個設置,例如:
<typeAliases>
<typeAlias type="com.mayikt.entity.User" alias="user"/>
</typeAliases>
3、Plugins
分頁插件配置
四、Mybatis大體架構流程分析
1、流程圖
1、讀取resources獲取對應的Reader對象。
reader = Resources.getResourceAsReader(resources);
2、使用SqlSessionFactoryBuilder獲取SqlSessionFactory源碼分析
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
源碼分析:
- Reader reader = Resources.getResourceAsReader(resources);
調用javaioAPI 讀取resources配置文件,獲取InputStreamReader
2、SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
3、使用XMLConfigBuilder 解析mybatis配置文件
SqlSessionFactoryBuilder使用XMLConfigBuilder解析配置文件,封裝成Configuration對象。
4、因為在構造函數設置了parsed 為fasle,XMLConfigBuilder 只能被使用一次。
調用該方法解析mybatis_config文件
通過反射機制匹配接口
注意:XMLConfigBuilder運行之后,只能被解析一次 否則會拋出異常。
xml轉換程bean對象 configuration
XMLConfigBuilder的作用是:解析mybatis配置文件文件 得到configuration
XMLMapperBuilder的作用是什么: 解析mybatisMapper文件
建議很多源碼中設置值都是采用構造函數形式
loadedResource 存放都是mybatis映射的文件路由地址 使用set集合存放
1.protected final Set<String> loadedResources = new HashSet<String>();
注意:mapper文件配置的namespace一定要和接口對應 否則情況查找失敗!
2mapperRegistry作用存放dao層mapper接口 底層使用過map集合存放。
解析配置文件完成了之后,都會裝配到configuration
Configuration作用:mybatis核心的配置文件內容 ,使用xml轉換bean
Mybatis掃包方式有兩種一種是 寫package、和resource
明白了 mapperRegistry 注冊我們的Mapper文件。
使用configuration獲取默認的DefaultSqlSessionFactory
五、MybatisMapper接口綁定原理
Mapper既然是接口,沒有被初始化如何被調用的?
答案: 使用動態代理技術
public static void main(String[] args) throws IllegalAccessException, InstantiationException { System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); UserMapper userMapper = getMapper(UserMapper.class); UserEntity user = userMapper.getUser(1); System.out.println("user:" + user.toString()); } //1.獲取對應的Mapper接口
public static <T> T getMapper(Class<T> clas) throws IllegalArgumentException, InstantiationException, IllegalAccessException { return (T) Proxy.newProxyInstance(clas.getClassLoader(), new Class[]{clas}, new MyBatisJdkInvocationHandler(clas)); }
public class MyBatisJdkInvocationHandler implements InvocationHandler { /** * 目標對象 */
private Object target; public MyBatisJdkInvocationHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return new UserEntity(1000l, "螞蟻課堂", 20); } /** * 獲取代理對象接口 * * @param <T> * @return
*/
public <T> T getProxy() { return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } }
接口綁定源碼分析:
解析節點信息封裝到Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>(),然后添加到configuration里面
轉換成Java類就是一個MappedStatement(存sql信息)
使用Configuration的getMappedStatement方法來獲取MappedStatement對象
獲取的方式key的組成為命名空
getMapper接口
從mapperRegistry獲取查詢接口對應的綁定接口
1、檢查是否已經注冊過Mapper接口
2、使用MapperProxyFactory 創建代理類MapperProxy
UserMapper.getUser方法的時候 調用MapperProxy的invoke方法
因為mapperRegister中 key:mapper接口 value MapperProxyFactory
使用MapperProxyFacotory創建MapperProxy代理
Mybatis基於多個不同的接口生成代理類 不同接口肯定不同的invoke方法
相同的接口,不同的方法肯定是走同一個invoke方法。
UserMapper.getUser()執行原理分析
1、調用MapperPrxoxy的invoke方法()
2、實現接口方法與配置文件sql語句關聯
底層使用的是:result = sqlSession.selectOne(command.getName(), param);
大致原理分析:
SqlSession提供select/insert/update/delete方法,在舊版本中使用使用SqlSession接口的這些方法,但是新版的Mybatis中就會建議使用Mapper接口的方法。
射器其實就是一個動態代理對象,進入到MapperMethod的execute方法就能簡單找到SqlSession的刪除、更新、查詢、選擇方法,
從底層實現來說:通過動態代理技術,讓接口跑起來,之后采用命令模式,最后還是采用了SqlSession的接口方法(getMapper()方法等到Mapper)執行SQL查詢
(也就是說Mapper接口方法的實現底層還是采用SqlSession接口方法實現的)。
五、總結:
- 獲取本地InputStreamReader對象(mybatis配置文件)
- 調用SqlSessionFactoryBuilder
- ###在使用XMLConfigBuilder解析mybatis配置文件,裝配到Configuration中。
- 將配置文件中的Mapper添加到Configuration mapperRegistry實現注冊。
備注:mapperRegistry存放當前所有的mapper文件。
5.使用Configuration獲取默認的DefaultSqlSessionFactory
六、MybatisMapper SQLSession源碼分析
1、SQLSession的作用
SqlSession提供select/insert/update/delete方法,在舊版本中使用使用SqlSession接口的這些方法
每個線程都應該有它自己的 SqlSession 實例。SqlSession 的實例不是線程安全的,因此是不能被共享的。
2、Executor執行器原理分析
1.openSessionFromDataSource,首先是從 Configuration 中取出相關的配置,生成 Transaction,接着又創建了一個 Executor,最后返回了 DefaultSqlSession 對象。
2.SimpleExecutor: 默認的 Executor,每個 SQL 執行時都會創建新的 Statement
ResuseExecutor: 相同的 SQL 會復用 Statement
BatchExecutor: 用於批處理的 Executor
CachingExecutor: 可緩存數據的 Executor,用代理模式包裝了其它類型的 Executor
默認情況下使用緩存的CachingExecutor
創建的openSession源碼分析:
1、創建事務管理器
2、創建執行器、
默認是創建簡單的執行器會變成緩存執行器呢
最好交給DefaultSqlSession
默認創建SimpleExecutor執行器 ,判斷是否開啟二級緩存,如果開啟了二級緩存
CachingExecutor執行器構造函數傳遞SimpleExecutor
如果二級緩存沒有,走簡單執行器
3、SelectOne底層原理查詢分析
- 當查詢單條數據的時候,最終還是調用selectList查詢多個結果集包裝程單個對象。
- 從configuration中獲取到MappedStatement(對應的sql語句配置),調用executor的query方法實現執行。
先查詢二級緩存,是否有緩存,沒有的話調用delegate.<E> query
如果一級緩存中沒有該結果,會調用queryFromDatabase查詢數據庫得到數據讓后在緩存到一級緩存中,下次查詢的時候相同的sql語句直接走一級緩存不會查詢數據庫。
一級(sqlSession緩存)和二級緩存(sessionFactory)
為什么CachingExecutor需要找到SimpleExecutor創建緩存key呢? 方便實現緩存key代碼重構
mybatis緩存控制 先查找二級緩存(硬盤、redis)、二級緩存沒有的情況在查找一級緩存。
一級緩存絕對是有的 ,但是二緩存可以沒有。
PerpetualCache 指的就是我們的一級 一級緩存屬於本地緩存 存放在內存中 使用map集合存放
4、Mybatis一級與二級緩存
一級緩存實現原理
相同查詢sql語句和參數
第一次查詢的時候 會調用數據庫的查詢 ,緩存到本地內存中
第二次查詢的時候 直接走本地內存 不會查詢數據庫。
sqlSession緩存為了防止臟數據,增加、修改、刪除的時候 都會清楚所有本地一級緩存。
mybatis的一級緩存是SqlSession級別的緩存,在操作數據庫的時候需要先創建SqlSession會話對象,
在對象中有一個HashMap用於存儲緩存數據,此HashMap是當前會話對象私有的,別的SqlSession會話對象無法訪問。
具體流程:
1.第一次執行select完畢會將查到的數據寫入SqlSession內的HashMap中緩存起來
2.第二次執行select會從緩存中查數據,如果select相同切傳參數一樣,那么就能從緩存中返回數據,不用去數據庫了,從而提高了效率
注意事項:
- 如果SqlSession執行了DML操作(insert、update、delete),並commit了,那么mybatis就會清空當前SqlSession緩存中的所有緩存數據,
這樣可以保證緩存中的存的數據永遠和數據庫中一致,避免出現臟讀
- 當一個SqlSession結束后那么他里面的一級緩存也就不存在了,mybatis默認是開啟一級緩存,不需要配置
- mybatis的緩存是基於[namespace:sql語句:參數]來進行緩存的,意思就是,SqlSession的HashMap存儲緩存數據時,是使用[namespace:sql:參數]作為key
注意:服務器集群的時候,每個sqlSession有自己獨立的緩存相互之間不存在共享,所以在服務器集群的時候容易產生數據沖突問題。
一級存在那些問題呢? 線程安全問題
一級緩存不共享 二級緩存存在共享
配置以下配置可以實現開啟日志打印
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<setting name="cacheEnabled" value="false"/>
</settings>
如何禁止一級緩存
方案1 在sql語句上 隨機生成 不同的參數 存在缺點:map集合可能爆 內存溢出的問題
方案2 開啟二級緩存
方案3 使用sqlSession強制清除緩存
方案4 創建新的sqlSession連接。
二級緩存SessionFactory
二級緩存是mapper級別的緩存,也就是同一個namespace的mappe.xml,當多個SqlSession使用同一個Mapper操作數據庫的時候,得到的數據會緩存在同一個二級緩存區域,二級緩存默認是沒有開啟的。
需要在setting全局參數中配置開啟二級緩存
Config.配置
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
在UserMapper配置
<!-- 以下兩個<cache>標簽二選一,第一個可以輸出日志,第二個不輸出日志 -->
<cache type="org.mybatis.caches.ehcache.LoggingEhcache" />
<!-- <cache type="org.mybatis.caches.ehcache.EhcacheCache"/> -->
二級緩存回收策略
LRU:最近最少使用的策略,移除最長時間不被使用的對象。
FIFO:先進先出策略,按對象進入緩存的順序來移除它們。
SOFT:軟引用策略,移除基於垃圾回收器狀態和軟引用規則的對象。
WEAK:弱引用策略,更積極地移除基於垃圾收集器狀態和弱引用規則的對象。
軟引用與弱引用的區別:
軟引用: 軟引用是用來描述一些有用但並不是必需的對象, 對於軟引用關聯着的對象,只有在內存不足的時候JVM才會回收該對象
弱引用: 弱引用也是用來描述非必需對象的,當JVM進行垃圾回收時,無論內存是否充足,都會回收被弱引用關聯的對象
TransactionalCache
TransactionalCache:繼承自Cache接口,主要作用是保存SqlSession在事務中需要向某個二級緩存提交的緩存數據(因為事務過程中的數據可能會回滾,所以不能直接把數據就提交二級緩存,而是暫存在TransactionalCache中,在事務提交后再將過程中存放在其中的數據提交到二級緩存,如果事務回滾,則將數據清除掉)
TransactionalCacheManager
TransactionalCacheManager:用於管理CachingExecutor使用的二級緩存對象,只定義了一個transactionalCaches字段
private final Cache delegate; //對應的二級緩存對象
private boolean clearOnCommit; //是否在commit時清除二級緩存的標記
// 需要在commit時提交到二級緩存的數據
private final Map<Object, Object> entriesToAddOnCommit;
// 緩存未命中的數據,事務commit時,也會放入二級緩存(key,null)
private final Set<Object> entriesMissedInCache;
StatementHandler
StatementHandler接口的實現大致有四個,其中三個實現類都是和JDBC中的Statement響對應的:
SimpleStatementHandler,這個很簡單了,就是對應我們JDBC中常用的Statement接口,用於簡單SQL的處理; 存在sql注入攻擊問題
PreparedStatementHandler,這個對應JDBC中的PreparedStatement,預編譯SQL的接口;
防止sql注入
CallableStatementHandler,這個對應JDBC中CallableStatement,用於執行存儲過程相關的接口;
RoutingStatementHandler,這個接口是以上三個接口的路由,沒有實際操作,只是負責上面三個StatementHandler的創建及調用。
ResultSetHandler
就是將Statement實例執行之后返回的ResultSet結果集轉換成我們需要的List結果集
一級緩存與二級緩存區別
①、一級緩存是SqlSession級別的緩存。在操作數據庫時需要構造sqlSession對象,在對象中有一個數據結構(HashMap)用於存儲緩存數據。
不同的sqlSession之間的緩存數據區域(sqlHashMap)是互相不影響的。
②、二級緩存是mapper級別的緩存,多個SqlSession去操作同一個Mapper的語句,多個SqlSession可以共用二級緩存,二級緩存是跨SqlSession的。
注意:sqlSession緩存底層存在線程安全問題。
七、Mybatis使用常用設計模式
Builder模式,例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;
工廠模式,例如SqlSessionFactory、ObjectFactory、MapperProxyFactory;
單例模式,例如ErrorContext和LogFactory;
代理模式,Mybatis實現的核心,比如MapperProxy、ConnectionLogger,用的jdk的動態代理;還有executor.loader包使用了cglib或者javassist達到延遲加載的效果;
組合模式,例如SqlNode和各個子類ChooseSqlNode等;
模板方法模式,例如BaseExecutor和SimpleExecutor,還有BaseTypeHandler和所有的子類例如IntegerTypeHandler;
適配器模式,例如Log的Mybatis接口和它對jdbc、log4j等各種日志框架的適配實現;
裝飾者模式,例如Cache包中的cache.decorators子包中等各個裝飾者的實現;
迭代器模式,例如迭代器模式PropertyTokenizer;
八、流程圖總結