【筆記】拉勾Java工程師高薪訓練營-第一階段 開源框架源碼解析-模組一 持久層框架涉及實現及MyBatis源碼分析-任務二:Mybatis基礎回顧及高級應用
- 2020 年 11 月 16 日
- 筆記
- Java高薪訓練營筆記
以下筆記是我看完影片之後總結整理的,部分較為基礎的知識點也做了補充,如有問題歡迎溝通。
- 任務二:Mybatis基礎回顧及高級應用
- 2.1 Mybatis相關概念回顧
- 2.2 Mybatis環境搭建回顧
- 2.3 MybatisCRUD回顧
- 2.4 Mybatic相關配置文件回顧
- 2.5 Mybatis相關API回顧
- 2.6 Mybatic的dao層傳統開發方式
- 2.7 Mybatic的dao層代理開發方式
- 2.8 Mybatis的properties深入
- 2.9 Mybatis的typeAliases深入
- 2.10 Mybatis的動態SQL-if標籤回顧
- 2.11 Mybatis的動態SQL-foreach標籤回顧
- 2.12 Mybatis的複雜映射-一對一映射
- 2.13 Mybatis的複雜映射-一對多映射
- 2.14 Mybatis的複雜映射-多對多映射
- 2.15 Mybatis註解CRUD回顧
- {}的值要與實體的保持一致,這樣才能調用get方法
- 2.16 Mybatis註解一對一回顧
- 2.17 Mybatis註解一對多回顧
- 2.18 Mybatis註解多對多回顧
- 2.19 Mybatis快取概念回顧
- 2.20 Mybatis一級快取回顧
- 2.21 Mybatis一級快取原理與源碼分析
- 2.22 Mybatis二級快取回顧
- 2.23 Mybatis利用redis實現二級快取
- 2.24 Mybatis-redisCache源碼分析
- 2.25 Mybatis插件機制介紹與原理
- 2.26 Mybatis插件機制-自定義Mybatis插件
- 2.27 Mybatis插件機制-plugin源碼分析
- 2.28 Mybatis插件機制-pageHelper
- 2.29 Mybatis插件機制-通用mapper
任務二:Mybatis基礎回顧及高級應用
2.1 Mybatis相關概念回顧
基於ORM(對象/關係資料庫映射)的半自動輕量級持久層框架
使用ORM的效果:操作實體類,等同於操作資料庫表
半自動的效果:支援訂製化SQL語句,擁有對SQL優化的權力
2.2 Mybatis環境搭建回顧
環境搭建包括以下六步:
1 添加Mybatis和MySql連接的依賴包到pom文件
2 創建user數據表
3 編寫User實體類
4 編寫映射文件Usermapper.xml
5 編寫核心文件SqlMapConfig.xml
6 編寫測試類
2.3 MybatisCRUD回顧
執行增刪改操作之後,要提交事務,相應操作才能生效
@Test
public void test1() throws IOException {
// 1 Resources工具類,載入配置文件,將配置文件載入成位元組輸入流
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 2 解析了配置文件,並創建SqlSessionFactory工廠
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 3 生產SqlSession,openSession默認會開啟一個事務,但不會提交
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4 sqlSession調用CRUD方法
List<User> users = sqlSession.selectList("user.findAll");
for (User user : users) {
System.out.println(user);
}
// 5 手動提交事務,如果要自動提交事務,創建sqlSession時需要sqlSessionFactory.openSession(true)
sqlSession.commit();
sqlSession.close();
}
2.4 Mybatic相關配置文件回顧
#{ }調用的就是對象的get方法
UserMapper.xml文件內容回顧:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"//mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="user">
<!--
namespace : 名稱空間,與id組成sql的唯一標識
parameterType : 參數類型
resultType : 返回值類型
-->
<!--查詢用戶-->
<select id="findAll" resultType="com.jlgl.pojo.User">
select * from user
</select>
<!--添加用戶-->
<insert id="saveUser" parameterType="com.jlgl.pojo.User">
insert into user values(#{id},#{username},#{password},#{birthday})
</insert>
<!--修改-->
<update id="updateUser" parameterType="com.jlgl.pojo.User">
update user set username=#{username} where id= #{id}
</update>
<!--刪除-->
<!--只有一個參數傳入,且為基本類型時,形參可以隨便寫-->
<delete id="deleteUser" parameterType="java.lang.Integer" >
delete from user where id=#{id}
</delete>
</mapper>
sqlMapConfig.xml文件內容回顧:
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"//mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--配置運行環境-->
<environments default="development">
<environment id="development">
<!--當前事務交由JDBC管理-->
<transactionManager type="JDBC"/>
<!--當前使用Mybatis提供的連接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://120.48.8.213:3306/audio_2020"/>
<property name="username" value="root"/>
<property name="password" value="root1028*1"/>
</dataSource>
</environment>
</environments>
<!--引入映射配置文件-->
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
2.5 Mybatis相關API回顧
// 1 Resources工具類,載入配置文件,將配置文件載入成位元組輸入流
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 2 解析了配置文件,並創建SqlSessionFactory工廠
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 3 生產SqlSession,openSession默認會開啟一個事務,但不會提交
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4 sqlSession調用CRUD方法
List<User> users = sqlSession.selectList("user.findAll");
for (User user : users) {
System.out.println(user);
}
// 5 手動提交事務,如果要自動提交事務,創建sqlSession時需要sqlSessionFactory.openSession(true)
sqlSession.commit();
sqlSession.close();
2.6 Mybatic的dao層傳統開發方式
具體包括以下三步:
1 創建介面IUserDao,聲明CRUD方法
2 創建實現類UserDaoImpl,實現IUserDao介面,完成CRUD方法的重寫
3 在業務邏輯中調用對應CRUD方法
2.7 Mybatic的dao層代理開發方式
只用編寫持久層介面即可,不需要編寫介面實現類,是通過動態代理對象完成CRUD操作
(下面的截圖中有個錯誤,介面名和mapper的namespace裡面應該同名,都為UserDao或UserMapper)
1) Mapper.xm文件中的 namespace 與 mapper 介面的全限定名相同
2) Mapper 介面方法名和 Mapper.xml中定義的每個 statement 的 id 相同
3) Mapper 介面方法的輸入參數類型和 mapper.xml 中定義的每個 sql 的 parameterType 的類型相同
4) Mapper 介面方法的輸出參數類型和 mapper.xml 中定義的每個 sql 的 resultType 的類型相同
2.8 Mybatis的properties深入
載入外部配置文件,必須放在第一行
2.9 Mybatis的typeAliases深入
XXXMapper.xml的resultType和parameterType必須要寫全限定類名比較麻煩,可以給一個別名
如果pojo數量較大,採用package批量起別名,別名就是包下的類名,且別名不區分大小寫
別名的配置是寫在sqlMapConfig.xml文件里
<typeAliases>
<!--給一個類起別名-->
<!--<typeAlias type="com.jlgl.pojo.User" alias="user"></typeAlias>-->
<!--批量起別名,別名就是包下的類名,切別名不區分大小寫-->
<package name="com.jlgl.pojo"/>
</typeAliases>
對於常用類型,Mybatis已經起好了別名
2.10 Mybatis的動態SQL-if標籤回顧
直接使用if標籤,需要在sql語句的最後先加一個where 1=1,然後再跟
select * from user where;
但是where 1=1 的寫法雖然能行,但還是不太優雅,所以可以用
<!--多條件組合查詢-->
<select id="findByCondition" parameterType="user" resultType="user">
select * from user
<where>
<if test="id!=null">
and id=#{id}
</if>
<if test="username !=null">
and username=#{username}
</if>
</where>
</select>
2.11 Mybatis的動態SQL-foreach標籤回顧
多值查詢,就是比如要查多個id的user,然後將他們封裝進一個List
<!--多值查詢-->
<select id="findByIds" parameterType="list" resultType="user">
select * from user
<where>
<foreach collection="array" open="id in (" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
SQL片段抽取,由於” select * from user “這段內容頻繁出現,因此可以將這一片段抽取出來以便復用
<!--抽取SQL片段-->
<sql id="selectUser">
select * from user
</sql>
<!--多值查詢-->
<select id="findByIds" parameterType="list" resultType="user">
<include refid="selectUser"></include>
<where>
<foreach collection="array" open="id in (" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
2.12 Mybatis的複雜映射-一對一映射
一個訂單對應一個用戶,查詢一個訂單並查出對應用戶資訊
希望將User資訊封裝到Order的User屬性里,resultType=”order”直接這麼寫,無法實現查詢結果到Order的封裝,因為User對象封裝不進去,所以要使用resultMap
<select id="findOrderAndUser" resultType="order">
SELECT * from orders o,user u WHERE o.uid=u.id
</select>
resultMap:手動配置實體屬性與表欄位的映射關係
resultMap各個屬性的含義:
id:給這個resultMap起一個名字,用於之後引用
type:被封裝數據所對應的全限定類名
result各個屬性的含義:
property:封裝對象的類屬性名
column:對應表的欄位名
association(resultMap裡面某個屬性類的相關資訊)各個屬性含義:
property:對應屬性名
javaType:屬性對應的Java類全限定類名
<resultMap id="orderMap" type="com.jlgl.pojo.Order">
<result property="id" column="id"></result>
<result property="ordertime" column="ordertime"></result>
<result property="total" column="total"></result>
<association property="user" javaType="com.jlgl.pojo.User">
<result property="id" column="uid"></result>
<result property="username" column="username"></result>
</association>
</resultMap>
引入映射文件,也可以用
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.jlgl.dao.IUserMapper.findOrderAndUser
at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:225)
at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:48)
at org.apache.ibatis.binding.MapperProxy.cachedMapperMethod(MapperProxy.java:65)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:58)
at com.sun.proxy.$Proxy5.findOrderAndUser(Unknown Source)
2.13 Mybatis的複雜映射-一對多映射
因為封裝的是集合,就不能使用association,而是collection
user和order都有id,那麼在IUserMapper.xml裡面如何知道哪個id封裝給對應的id呢?需要做個區分column=”uid”
<resultMap id="userMap" type="com.jlgl.pojo.User">
<result property="id" column="uid"></result>
<result property="username" column="username"></result>
<!-- 因為封裝的是集合,就不能使用association,而是collection -->
<collection property="orderList" ofType="com.jlgl.pojo.Order">
<result property="id" column="id"></result>
<result property="ordertime" column="ordertime"></result>
<result property="total" column="total"></result>
<result property="uid" column="uid"></result>
</collection>
</resultMap>
2.14 Mybatis的複雜映射-多對多映射
用戶角色模型,一個用戶有多個角色,一個角色被多個用戶使用,查詢需求是查詢所有用戶的同時查到關聯的所有角色
user表
role表
user_role表,用戶角色中間表,至少兩個欄位,這兩個欄位作為聯合主鍵,也作為外鍵,分別指向另外兩張表的主鍵
2.15 Mybatis註解CRUD回顧
使用註解開發,無需再編寫配置文件
@Insert::實現新增
@Update:實現更新
@Delete:實現刪除
@Select:實現ັ查詢
@Result:實現結果封裝
@Resultsғ:可以和@Result一起使用,封裝多個結果集
@One:實現一對一結果集封裝
@Many:實現一對多結果集封裝
{}的值要與實體的保持一致,這樣才能調用get方法
Junit的@Before的作用是在執行@Test之前執行一些邏輯
2.16 Mybatis註解一對一回顧
@One對應的是association
one=@One(select = “namespace.id”)要做的就是根據uid,再去查詢關聯的用戶
所以要在IUserMapper裡面寫好根據id查詢的語句
即用了註解,也使用了xml配置文件,就會引起衝突,所以只能用一個
// 查詢訂單的同時還查詢該訂單所屬用戶
@Results({
@Result(property = "id", column = "id"),
@Result(property = "ordertime", column = "ordertime"),
@Result(property = "total", column = "total"),
@Result(property = "user", column = "uid", javaType = User.class,
one = @One(select = "com.jlgl.dao.IUserMapper.findUserById"))})
@Select("select * from orders")
List<Order> findOrderAndUser();
2.17 Mybatis註解一對多回顧
較為基礎,不再贅述
2.18 Mybatis註解多對多回顧
較為基礎,不再贅述
2.19 Mybatis快取概念回顧
快取就是記憶體中的一些數據,數據來自對資料庫查詢結果的快取,可以避免與資料庫的直接交互
一級快取的級別就是sqlSession,底層結構就是一個Map
二級快取的範圍跨sqlSession,是mapper級別(namespace級別)
2.20 Mybatis一級快取回顧
一級快取默認是開啟的
public void firstLevelCache(){
// 使用debug,看控制台的日誌輸出
// 第一次查詢
User userById1 = userMapper.findUserById(1);
// 第二次查詢
User userById2 = userMapper.findUserById(1);
System.out.println(userById1 == userById2);
}
結論1:第一次查詢,若快取中沒有,則會從資料庫查詢,然後存到快取中
結論2:做增刪改操作,並進行了事務提交,就會刷新一級快取
結論3:sqlSession.clearCache()也可以直接清空以及快取,從而直接從資料庫查詢
2.21 Mybatis一級快取原理與源碼分析
看源碼之前,要帶著這幾個問題:
問題1:一級快取究竟是什麼
問題2:什麼時候被創建
問題3:一級快取的工作流程是什麼
分析源碼的時候要先找到入口,這裡就是SqlSession
翻閱源碼的時候,要注意類與類之間的關係,比如從SqlSession介面裡面的clearCache,我們要找到對應的實現類,
問題1:一級快取究竟是什麼
答案:快取就是一個HashMap對象
能夠看到private Map<Object, Object> cache = new HashMap();,所以快取就是一個HashMap對象
private Map<Object, Object> cache = new HashMap();
問題2:什麼時候被創建
答案:
Executor就是執行器,所以一般應該在這裡創建
CacheKey cacheKey = new CacheKey();
// 其實就是statementId
cacheKey.update(ms.getId());
// 分頁參數
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
// 底層的sql語句
cacheKey.update(boundSql.getSql());
// 還有一個獲取environment的程式碼也值得關注一下
if (this.configuration.getEnvironment() != null) {
cacheKey.update(this.configuration.getEnvironment().getId());
}
問題3:一級快取的工作流程是什麼
答案:Executor的query方法裡面具體創建、查詢了一級快取
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
if (list != null) {
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
2.22 Mybatis二級快取回顧
二級快取需要配置才會開啟,一級快取是默認開啟的
配置需要兩步:
1 sqlMapConfig.xml裡面配置
2 在xxxMapper.xml或介面配置開啟二級快取
<!--開啟二級快取-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
// 開啟二級快取
@CacheNamespace
public interface IUserMapper {
}
//如果要禁用快取,要求每次都從資料庫查詢
@Options(useCache = false)
@Select("select * from user where id=#{id}")
public User findUserById(Integer id);
2.23 Mybatis利用redis實現二級快取
利用redis實現二級快取需要三步:
1 啟動redis
2 導入Mybatis實現的redisCache或自己編寫redisCache
3 連接redis
4 更換註解裡面的implemention
5 執行程式碼
PerpetualCache這個類就是Mybatis默認實現快取的類
不管是PerpetualCache,還是我們要自定義快取類,都要實現Cache介面
@CacheNamespace(implementation = PerpetualCache.class)可以指定實現類
public interface IUserMapper {
}
Mybatis自帶的二級快取,是為單伺服器工作的,無法實現分散式快取,所以我們要通過redis實現分散式二級快取,不過Mybatis已經實現好了
@CacheNamespace(implementation = RedisCache.class)
public interface IUserMapper {
}
2.24 Mybatis-redisCache源碼分析
看源碼之前,要帶著這幾個問題:
問題1:如何進行快取的存取
問題2:存儲使用的是怎樣的數據結構
redis.properties,且必須在resources文件夾下
通過程式碼可以看出,使用的是hset,即哈希表
public void putObject(final Object key, final Object value) {
this.execute(new RedisCallback()
{
public Object doWithRedis(Jedis jedis) {
jedis.hset(RedisCache.this.id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));
return null;
}});
}
2.25 Mybatis插件機制介紹與原理
一般情況下,開源框架都會提供插件或其他形式的拓展點,供開發者自行拓展。這樣的好處是顯而易見的,一是增加了框架的靈活性開發者可以結合實際需求,對框架進行拓展,使其能夠更好的工作。
Mybatis的四大組件分別是:
Executor̵
StatementHandler̵
ParameterHandler̵
ResultSetHandler
插件就是通過對四大核心組件進行攔截,然後增強功能,而底層使用的就是動態代理
如果我們需要自定義插件,可以如此定義:
2.26 Mybatis插件機制-自定義Mybatis插件
自定義插件編寫步驟:
1 編寫程式碼
2 sqlMapConfig註冊插件
@Intercepts({
// 可以配置多個
@Signature(type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class})
})
public class Myplugin implements Interceptor {
/**
* 攔截方法:只要攔截的目標對象的目標方法被執行,就會執行intercept方法
*
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("對方法進行了增強");
// 這句話就是讓原方法執行
return invocation.proceed();
}
/**
* 把當前的攔截器生成代理對象,存到攔截器中
*
* @param o
* @return
*/
@Override
public Object plugin(Object o) {
Object wrap = Plugin.wrap(o, this);
return wrap;
}
/**
* 獲取配置文件的參數
*
* @param properties
*/
@Override
public void setProperties(Properties properties) {
System.out.println("獲取到的配置文件的參數是:"+properties);
}
}
sqlMapConfig註冊插件
<!--配置插件-->
<plugins>
<plugin interceptor="com.jlgl.plugin.Myplugin">
<property name="name" value="tom"/>
</plugin>
</plugins>
控制台輸出:
獲取到的配置文件的參數是:{name=tom}
對方法進行了增強
2.27 Mybatis插件機制-plugin源碼分析
入口Plugin,通過implements InvocationHandler能夠看出,這個對象就是動態代理對象處理類,並一定是重寫了invoke方法
public class Plugin implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
} catch (Exception var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}
}
2.28 Mybatis插件機制-pageHelper
分頁插件使用需要三步:
1 導入pageHelper到pom
2 sqlMapConfig註冊插件,需要配置方言
3 編寫查詢程式碼
sqlMapConfig註冊插件,需要配置方言
<plugin interceptor="com.github.pagehelper">
<!--因為不同資料庫的sql分頁語句不同,所以要配置對應的方言-->
<property name="dialect" value="mysql"/>
</plugin>
Java查詢程式碼
@Test
public void pageHelperTest(){
PageHelper.startPage(1,2);
List<User> users = userMapper.selectUser();
for (User user : users) {
System.out.println(user);
}
// 可以查詢獲得很多分頁相關的數據
PageInfo<User> userPageInfo = new PageInfo<>(users);
userPageInfo.getFirstPage();
}
2.29 Mybatis插件機制-通用mapper
單表增刪改查的數據重複書寫沒有意義,可以直接使用通用mapper,只要寫好實體類就可以了(當然實體類的生成也不需要手寫,也有對應的自動化生成方法)
注意Example和Criteria的操作
此部分較為基礎,不再贅述