mybatis學習總結
一, day01學習小結
1.1 三層架構和ssm架構
1.什麼是架構?使用架構有什麼好處?2.三層架構分别指的是什麼?
架構作為一個半成品軟體,它是我們軟體開發過程中的一套解決方案,不同架構解決不同的問題.
好處:架構封裝了很多的細節,使開發者可以用極簡的方式實作功能,可以大大提高開發效率以及提高軟體開發品質.
表現層(Web層):用于向使用者展示相關資料資訊(如SpringMVC架構);
業務層(Service層):用于處理業務核心邏輯;
持久層(Dao層):用于與資料庫進行互動(如Mybatis,Struts架構);
備注:準确來說Spring不屬于任何一層.
1.2 Mybatis概述
-
基本資訊
- MyBatis 是一款優秀的持久層架構,它支援定制化 SQL、存儲過程以及進階映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設定參數以及擷取結果集。MyBatis 可以使用簡單的 XML 或注解來配置和映射原生資訊,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java對象)映射成資料庫中的記錄.
- 它使用ORM(Object Relational Mapping對象關系映射)思想實作了結果集的封裝.簡單來說,就是資料庫表的一條記錄對應一個對象.(資料庫中的表和列 <--------對應---------> 實體類和成員變量)
-
優點
- 簡單易學:本身就很小且簡單。沒有任何第三方依賴,最簡單安裝隻要兩個jar檔案+配置幾個sql映射檔案易于學習,易于使用,通過文檔和源代碼,可以比較完全的掌握它的設計思路和實作。
- 靈活:mybatis不會對應用程式或者資料庫的現有設計強加任何影響。 sql寫在xml裡,便于統一管理和優化。通過sql語句可以滿足操作資料庫的所有需求。
- 解除SQL與程式代碼的耦合:通過提供DAO層,将業務邏輯和資料通路邏輯分離,使系統的設計更清晰,更易維護,更易單元測試。SQL和代碼的分離,提高了可維護性。
- 提供映射标簽,支援對象與資料庫的orm字段關系映射
- 提供對象關系映射标簽,支援對象關系組建維護
- 提供xml标簽,支援編寫動态SQL。
-
功能架構
我們把Mybatis的功能架構分為三層:
- API接口層:提供給外部使用的接口API,開發人員通過這些本地API來操縱資料庫。接口層一接收到調用請求就會調用資料處理層來完成具體的資料處理。
- 資料處理層:負責具體的SQL查找、SQL解析、SQL執行和執行結果映射處理等。它主要的目的是根據調用的請求完成一次資料庫操作。
- 基礎支撐層:負責最基礎的功能支撐,包括連接配接管理、事務管理、配置加載和緩存處理,這些都是共用的東西,将他們抽取出來作為最基礎的元件。為上層的資料處理層提供最基礎的支撐。
mybatis學習總結 mybatis學習總結
-
架構架構
- 加載配置:配置來源于兩個地方,一處是配置檔案,一處是Java代碼的注解,将SQL的配置資訊加載成為一個MappedStatement對象(包括了傳入參數映射配置、執行的SQL語句、結果映射配置),存儲在記憶體中。
- SQL解析:當API接口層接收到調用請求時,會接收到傳入SQL的ID和傳入對象(可以是Map、JavaBean或者基本資料類型),Mybatis會根據SQL的ID找到對應的MappedStatement,然後根據傳入參數對象對MappedStatement進行解析,解析後可以得到最終要執行的SQL語句和參數。
- SQL執行:将最終得到的SQL和參數拿到資料庫進行執行,得到操作資料庫的結果。
- 結果映射:将操作資料庫的結果按照映射的配置進行轉換,可以轉換成HashMap、JavaBean或者基本資料類型,并将最終結果傳回。
mybatis學習總結 mybatis學習總結
1.3 Mybatis入門
-
環境搭建
- 建立maven工程,導入相應的jar包坐标(MySQL驅動,Mybatis,Junit,Log4j);
- 建立對應資料庫的實體類和相應的Dao接口(如User類和IUserDao接口);
- 建立Mybatis主配置檔案SqlMapConfig.xml;
- 建立相應的SQL映射配置檔案.
- 注意事項: Mybatis映射檔案位置必須和dao接口的包結構一緻;映射檔案的mapper标簽namespace屬性值必須是dao的全限定類名;映射檔案的crud操作配置中,id取值必須是dao接口的方法名.
-
CRUD入門
public class MybatisTest { public static void main(String[] args) throws Exception{ // 1.使用ibatis的Resources.getResourceAsStream()方法來讀取主配置檔案 // SqlMapConfig.xml檔案,獲得InputStream流 InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); // 2.利用SqlSessionFactoryBuilder建構者的build()方法來擷取SqlSessionFactory工廠 // 類對象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = builder.build(is); // 3.利用SqlSessionFactory工廠的openSession()方法獲得SqlSession對 // 象,openSession()方法傳入true值可以開啟事務的自動送出,但是實際操作不推薦 SqlSession sqlSession = sqlSessionFactory.openSession(); // 4.利用SqlSession.getMapper(dao接口的位元組碼對象)獲得此dao接口的代理對象 IUserDao mapper = sqlSession.getMapper(IUserDao.class); // 5.調用代理對象的相應方法進行資料庫的crud操作 List<User> users = mapper.findAll(); for (User user : users) { System.out.println(user); } // 6.關閉SqlSession,InputStream資源 sqlSession.close(); is.close(); } }
-
#{}與${}的差別
mybatis學習總結 mybatis學習總結 -
mybatis中的設計模式
- Builder模式,例如SqlSessionFactoryBuilder、Environment;
- 工廠方法模式,例如SqlSessionFactory、TransactionFactory、TransactionFactory、LogFactory、ObjectFactory、ReflectorFactory;
- 單例模式,例如ErrorContext和LogFactory;
- 代理模式,Mybatis實作的核心,比如MapperProxy、ConnectionLogger,用的jdk的動态代理;還有executor.loader包使用了cglib或者javassist達到延遲加載的效果;
- 組合模式,例如SqlNode和各個子類ChooseSqlNode等;
- 模闆方法模式,例如BaseExecutor和SimpleExecutor,還有BaseTypeHandler和所有的子類例如IntegerTypeHandler;
- 擴充卡模式,例如Log的Mybatis接口和它對jdbc、log4j等各種日志架構的适配實作;
- 裝飾者模式,例如Cache包中的cache.decorators子包中等各個裝飾者的實作;
- 疊代器模式,例如疊代器模式PropertyTokenizer;
二, day02學習小結
2.1 mybatis的CRUD
-
IUserDao.xml映射檔案配置
<?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="com.hooware.dao.IUserDao">
<!-- 根據id删除使用者 -->
<delete id="deleteUser" parameterType="INT">
delete from user where id = #{uid}
</delete>
<!-- 根據id更新使用者 -->
<update id="updateUser" parameterType="com.hooware.domain.User">
update user set username = #{username}, sex = #{sex}, birthday = #{birthday} where id = #{id}
</update>
<!-- 根據id查詢使用者 -->
<select id="findById" parameterType="int" resultType="com.hooware.domain.User">
select * from user where id = #{uid}
</select>
<!-- 插入使用者資料并查詢插入使用者的id -->
<insert id="saveUser" parameterType="com.hooware.domain.User">
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
insert into user(username, birthday, sex, address) VALUES(#{username},
#{birthday}, #{sex}, #{address})
</insert>
<!-- 模糊查詢,QueryVo使用者封裝查詢的多個參數 -->
<select id="findByQueryVo" parameterType="com.hooware.domain.QueryVo" resultType="com.hooware.domain.User">
select * from USER where username like #{user.username};
</select>
</mapper>
2.2 實體類屬性和資料庫字段名不一緻問題
為什麼隻有userName能拿到資料,是因為MySQL在window環境下是不區分大小寫的,可以封裝,linux下區分大小寫,則無法進行封裝.
- 方案一:在映射檔案中給字段起别名,能夠保證屬性與字段對應一緻就可以,
- 方案二:建立ResultMap配置屬性與字段對應,如下:
- SQL語句給字段起别名:(執行效率高)
優點:SQL執行效率高
缺點:每個SQL語句都要改
- 配置ResultMap映射:(開發效率高)
優點: 隻配置一次,其他地方引用即可,開發效率高
缺點: 執行效率稍低
2.3 properties标簽的使用及細節
- 可以在标簽内部配置連接配接資料庫的資訊。也可以通過屬性引用外部配置檔案資訊.
- resource屬性: 用于指定配置檔案的位置,是按照類路徑的寫法來寫,并且必須存在于類路徑下。
- url屬性: 是要求按照Url的寫法來寫位址. (URL:Uniform Resource Locator 統一資源定位符。它是可以唯一辨別一個資源的位置. URI:Uniform Resource Identifier 統一資源辨別符。它是在應用中可以唯一定位一個資源的)
2.4 typeAliases标簽和package标簽
在映射檔案中是有預設的别名的,比如int,但是自己定義的User是不能使用别名的,要想使用别名,可以在主配置檔案中使用typeAliases配置自己的别名.
-
SqlMapConfig.xml配置
<?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>
<properties resource="jdbcConfig.properties" />
<!--延遲加載設定-->
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
<!--<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>-->
<!--<setting name="cacheEnabled" value="true"/>-->
</settings>
<typeAliases>
<package name="com.hooware.domain" />
</typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
</environments>
<!--映射檔案-->
<mappers>
<package name="com.hooware.dao" />
</mappers>
</configuration>
-
SQL映射檔案配置
<?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="com.hooware.dao.IUserDao">
<!--<!– 開啟二級緩存支援 –>-->
<!--<cache />-->
<resultMap id="userAccountMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="password" column="password"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<collection property="accounts" ofType="account" column="id" fetchType="lazy" select="com.hooware.dao.IAccountDao.findByUid">
</collection>
</resultMap>
<select id="findAll" resultMap="userAccountMap">
SELECT * from user
<!-- SELECT u.*, a.ID as aid, a.uid, a.money from user u LEFT JOIN account a on u.id = a.uid -->
</select>
<select id="findById" resultMap="userAccountMap" parameterType="int">
SELECT * from user where id = #{value}
</select>
</mapper>
三, day03學習小結
3.1 mybatis連接配接池的分類
- type=”POOLED” :MyBatis會建立PooledDataSource執行個體.
- type=”UNPOOLED” :MyBatis會建立UnpooledDataSource執行個體.
- type=”JNDI” :MyBatis會從JNDI服務上查找DataSource執行個體,然後傳回使用.
3.2 mybatis中使用poolead配置連接配接的原理分析
- 為什麼要使用帶連接配接池的資料源呢,最根本的原因還是因為每次建立連接配接開銷比較大,頻繁的建立和關閉資料庫連接配接将會嚴重的影響性能。是以,常用的做法是維護一個資料庫連接配接池,每次使用完之後并不是直接關閉資料庫連接配接,再後面如果需要建立資料庫連接配接的時候直接拿之前釋放的資料庫連接配接使用,避免頻繁建立和關閉資料庫連接配接造成的開銷。
- 在mybatis中,定義了一個資料庫連接配接池狀态的類PoolState,在這個類裡,除維護了資料源執行個體,還維護着資料庫連接配接。資料庫連接配接被分成了兩種狀态類型并存放在兩個清單中:idleConnections和activeConnections。
- idleConnections:
- 空閑(idle)狀态PooledConnection對象被放置到此集合中,表示目前閑置的沒有被使用的PooledConnection集合,調用PooledDataSource的getConnection()方法時,會優先從此集合中取PooledConnection對象。當用完一個java.sql.Connection對象時,MyBatis會将其包裹成PooledConnection對象放到此集合中。
- activeConnections:
- 活動(active)狀态的PooledConnection對象被放置到名為activeConnections的ArrayList中,表示目前正在被使用的PooledConnection集合,調用PooledDataSource的getConnection()方法時,會優先從idleConnections集合中取PooledConnection對象,如果沒有,則看此集合是否已滿,如果未滿,PooledDataSource會建立出一個PooledConnection,添加到此集合中,并傳回.
-
從PooledDataSource類擷取連接配接的過程
- 檢視如下源碼可以得出如下結論:
- 先看是否有空閑(idle)狀态下的PooledConnection對象,如果有,就直接傳回一個可用的PooledConnection對象;否則進行第2步。
- 檢視活動狀态的PooledConnection池activeConnections是否已滿;如果沒有滿,則建立一個新的PooledConnection對象,然後放到activeConnections池中,然後傳回此PooledConnection對象;否則進行第三步;
- 看最先進入activeConnections池中的PooledConnection對象是否已經過期:如果已經過期,從activeConnections池中移除此對象,然後建立一個新的PooledConnection對象,添加到activeConnections中,然後将此對象傳回;否則進行第4步;
- 線程等待,循環2步。
/* 調用了popConnection()方法,然後傳回其代理對象 */
@Override
public Connection getConnection() throws SQLException {
return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return popConnection(username, password).getProxyConnection();
}
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
PooledConnection conn = null;
long t = System.currentTimeMillis();
int localBadConnectionCount = 0;
while (conn == null) {
synchronized (state) {
if (!state.idleConnections.isEmpty()) {
// Pool has available connection
conn = state.idleConnections.remove(0);
if (log.isDebugEnabled()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
} else {
// Pool does not have available connection
if (state.activeConnections.size() < poolMaximumActiveConnections) {
// Can create new connection
conn = new PooledConnection(dataSource.getConnection(), this);
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else {
// Cannot create new connection
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
if (longestCheckoutTime > poolMaximumCheckoutTime) {
// Can claim overdue connection
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
state.activeConnections.remove(oldestActiveConnection);
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
try {
oldestActiveConnection.getRealConnection().rollback();
} catch (SQLException e) {
log.debug("Bad connection. Could not roll back");
}
}
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else {
// Must wait
try {
if (!countedWait) {
state.hadToWaitCount++;
countedWait = true;
}
if (log.isDebugEnabled()) {
log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
}
long wt = System.currentTimeMillis();
state.wait(poolTimeToWait);
state.accumulatedWaitTime += System.currentTimeMillis() - wt;
} catch (InterruptedException e) {
break;
}
}
}
}
if (conn != null) {
if (conn.isValid()) {
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
conn.setCheckoutTimestamp(System.currentTimeMillis());
conn.setLastUsedTimestamp(System.currentTimeMillis());
state.activeConnections.add(conn);
state.requestCount++;
state.accumulatedRequestTime += System.currentTimeMillis() - t;
} else {
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
}
state.badConnectionCount++;
localBadConnectionCount++;
conn = null;
if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Could not get a good connection to the database.");
}
throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
}
}
}
}
}
if (conn == null) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
return conn;
}
3.3 mybatis中的事務
- 事務的概念
事務:一個邏輯單元中的一組操作,要麼全部成功,要麼全部失敗.
- 事務的4個特性
(ACID)原子性、一緻性、隔離性和持久性
- 不考慮事務的隔離性可能産生的3個問題:
- 髒讀:一個事務,讀到另一個事務未送出的資料. (read uncommitted可能出現髒讀)
- 不可重複讀:一個事務讀到另一個事務已經送出的update的資料,導緻一個事務中多次查詢結果不一緻!
- 虛讀(幻讀):一個事務讀到另一個事務已經送出的insert資料,導緻一個事務中多次查詢的記錄條數不一緻!
MySQL資料庫的四種隔離級别:
① Serializable (串行化):可避免髒讀、不可重複讀、幻讀的發生。
② Repeatable read (可重複讀):可避免髒讀、不可重複讀的發生。
③ Read committed (讀已送出):可避免髒讀的發生。
④ Read uncommitted (讀未送出):最低級别,任何情況都無法保證。
以上四種隔離級别最高的是Serializable級别,最低的是Read uncommitted級别,當然級别越高,執行效率就越低。像Serializable這樣的級别,就是以鎖表的方式(類似于Java多線程中的鎖)使得其他的線程隻能在鎖外等待,是以平時選用何種隔離級别應該根據實際情況。在MySQL資料庫中預設的隔離級别為Repeatable read (可重複讀)。
在MySQL資料庫中,支援上面四種隔離級别,預設的為Repeatable read (可重複讀);而在Oracle資料庫中,隻支援Serializable (串行化)級别和Read committed (讀已送出)這兩種級别,其中預設的為Read committed級别。
3.4 mybatis中的動态SQL語句(if,where,foreach标簽)
- where标簽: 可以自動處理第一個and, 用了,SQL不用加"where 1 = 1".
- if标簽: test屬性中寫的是對象的屬性名,如果是包裝類的對象要使用OGNL表達式的寫法.
- foreach标簽: 用于周遊集合. 有如下屬性:
- collection:代表要周遊的集合元素,注意編寫時不要寫#{}
- open:代表語句的開始部分
- close:代表結束部分
- item:代表周遊集合的每個元素,生成的變量名
- sperator:代表分隔符
<!-- if,where,foreach标簽使用示例 -->
<select id="findByIds" resultType="user" parameterType="queryVo">
select * from user
<where>
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" open=" and id in (" close=")" item="uid" separator=",">
#{uid}
</foreach>
</if>
</where>
</select>
3.5 mybatis多表關系分析
- mybatis多表關系隻有3種:
- 一對一.(多對一在mybatis中也是一對一的關系)
- 一對多.
- 多對多.
-
賬戶和使用者的一對一關系
- 在Account實體類中加入一個user引用.
- 在IAccountDao.xml映射檔案的ResultMap中添加标簽,加入user類的映射關系.
<resultMap id="accountUserMap" type="account"> <id property="id" column="aid"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> <association property="user" column="uid" javaType="user"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="password" column="password"></result> <result property="birthday" column="birthday"></result> <result property="sex" column="sex"></result> <result property="address" column="address"></result> </association> </resultMap> <select id="findAll" resultMap="accountUserMap"> SELECT u.*, a.id as aid, a.uid, a.money from account a left join user u on a.uid = u.id </select>
-
使用者和賬戶的一對多關系
- 在User實體類中加入一個List accounts集合引用.
- 在IUserDao.xml.xml映射檔案的ResultMap中添加标簽,加入user類的映射關系.
<resultMap id="userAccountMap" type="user"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="password" column="password"></result> <result property="birthday" column="birthday"></result> <result property="sex" column="sex"></result> <result property="address" column="address"></result> <collection property="accounts" ofType="account"> <id property="id" column="aid"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> </collection> </resultMap> <select id="findAll" resultMap="userAccountMap"> SELECT u.*, a.ID as aid, a.uid, a.money from user u LEFT JOIN account a on u.id = a.uid </select>
-
使用者和角色的多對多關系
- 不管是使用者和角色的關系還是角色和使用者的關系,本質上都是一對多的關系,隻不過SQL語句要借助中間表來處理.具體操作參照如上一對多的步驟.
四, day04學習小結
4.1 延遲加載和立即加載
- 什麼是延遲加載?
在真正使用資料時才發起查詢,不用的時候不查詢。按需加載(懶加載).- 什麼是立即加載?
不管用不用,隻要一調用方法,馬上發起查詢.一對多,多對多:通常情況下我們都是采用延遲加載.
一對一:通常情況下我們都是采用立即加載.
4.2 延遲加載操作
-
一對一延遲加載
- 在SqlMapConfig.xml配置标簽,開啟延遲加載.
<!--延遲加載設定--> <settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> <!--<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>--> </settings>
注意: 指定對象的哪個方法觸發一次延遲加載在mybatis中,預設情況下隻要調用了equals,clone,hashCode,toString這幾個方法,都會對對象進行完全加載. 因為這裡我需要調用toString方法,檢視傳回的資料,又不想觸發完全加載,是以配置了一個hashCode<setting name="lazyLoadTriggerMethods" value="hashCode"/>
- 對應SQL映射檔案配置.
<resultMap id="accountUserMap" type="account"> <id property="id" column="aid"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> <association property="user" column="uid" javaType="user" fetchType="eager" select="com.hooware.dao.IUserDao.findById"> </association> </resultMap> <select id="findAll" resultMap="accountUserMap"> SELECT * FROM account <!-- SELECT u.*, a.id as aid, a.uid, a.money from account a left join user u on a.uid = u.id --> </select>
注意:
select : 填寫我們要調用的 select 映射的 id
column : 填寫我們要傳遞給 select 映射的參數
fetchType: eager立即加載, lazy延遲加載,可以覆寫全局設定的延遲加載政策.
javaType: 傳回值為User類型.
-
一對多延遲加載
- 在SqlMapConfig.xml配置标簽,開啟延遲加載.
<!--延遲加載設定--> <settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> <!--<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>--> </settings>
注意: 指定對象的哪個方法觸發一次延遲加載在mybatis中,預設情況下隻要調用了equals,clone,hashCode,toString這幾個方法,都會對對象進行完全加載. 因為這裡我需要調用toString方法,檢視傳回的資料,又不想觸發完全加載,是以配置了一個hashCode<setting name="lazyLoadTriggerMethods" value="hashCode"/>
- 對應SQL映射檔案配置.
<resultMap id="userAccountMap" type="user"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="password" column="password"></result> <result property="birthday" column="birthday"></result> <result property="sex" column="sex"></result> <result property="address" column="address"></result> <collection property="accounts" ofType="account" column="id" fetchType="lazy" select="com.hooware.dao.IAccountDao.findByUid"> </collection> </resultMap> <select id="findAll" resultMap="userAccountMap"> SELECT * from user <!-- SELECT u.*, a.ID as aid, a.uid, a.money from user u LEFT JOIN account a on u.id = a.uid --> </select>
注意:
select : 用于指定查詢 account 清單的 sql 語句,是以填寫的是該 sql 映射的 id
column : 用于指定 select 屬性的 sql 語句的參數來源,上面的參數來自于 user 的 id 列
fetchType: eager立即加載, lazy延遲加載,可以覆寫全局設定的延遲加載政策.
ofType: List泛型類型為account類型.
4.3 一級緩存和二級緩存
- 什麼是緩存
存在于記憶體中的臨時資料。
- 為什麼使用緩存
減少和資料庫的互動次數,提高執行效率。
什麼樣的資料能使用緩存,什麼樣的資料不能使用
适用于緩存:
經常查詢并且不經常改變的。
資料的正确與否對最終結果影響不大的。
不适用于緩存:
經常改變的資料
資料的正确與否對最終結果影響很大的。
例如:商品的庫存,銀行的匯率,股市的牌價。
SqlSession一級緩存:
- 它指的是Mybatis中SqlSession對象的緩存。當我們執行查詢之後,查詢的結果會同時存入到SqlSession為我們提供一塊區域中。該區域的結構是一個Map。當我們再次查詢同樣的資料,mybatis會先去sqlsession中 查詢是否有,有的話直接拿出來用。當SqlSession對象消失時,mybatis的一級緩存也就消失了。
- 當調用SqlSession的增删改,commit(),close(),clearCache()等方法時,就會清空一級緩存.
- 一旦目前sqlsession 執行update,delete,insert操作,把目前sqlsession緩存的所有資料,都清空,而不是清空修改的那一條資料!
- 在一個sqlSession範圍内, 緩存的資料有可能和資料庫不一緻的情況發生, 當一級緩存後,另外的sqlsession或者直接在資料庫修改後,會導緻緩存和資料庫兩邊資料不一緻!
SqlSessionFactory二級緩存
- 它指的是Mybatis中SqlSessionFactory對象的緩存。由同一個SqlSessionFactory對象建立的SqlSession共享其緩存。
二級緩存開啟步驟:
第一步:讓Mybatis架構支援二級緩存(在SqlMapConfig.xml中配置,預設開啟)
第二步:讓目前的映射檔案支援二級緩存(在IUserDao.xml中配置)<settings> <setting name="cacheEnabled" value="true"/> </settings>
第三步:讓目前的操作支援二級緩存(在select标簽中配置useCache=“true”)<!-- 開啟二級緩存支援 --> <cache />
- 二級緩存緩存的是資料而不是對象,從緩存中擷取到對象重新組裝成對象傳回是以當我們在使用二級緩存時,所緩存的類一定要實作java.io.Serializable接口,這種就可以使用序列化方式來儲存對象.
4.4 注解開發
注意: 對于dao 中的某個方法而言,不能注解和xml配置檔案同時使用!!!
-
一對一注解開發
public interface IAccountDao {
/**
* 查詢所有賬戶
* @return
*/
@Select(value="select * from account")
@Results(id = "accountMap", value = {
@Result(id = true, property = "id", column = "id"),
@Result(property = "uid", column = "uid"),
@Result(property = "money", column = "money"),
@Result(property = "user", column = "uid", one = @One(select = "com.hooware.dao.IUserDao.findById", fetchType = FetchType.EAGER))
})
List<Account> findAll();
/**
* 根據使用者ID查詢賬戶
* @param uid
* @return
*/
@Select("select * from account where uid = #{uid}")
@ResultMap("accountMap")
List<Account> findByUid(Integer uid);
}
-
一對多注解開發
//@CacheNamespace(blocking = true) // 開啟二級緩存
public interface IUserDao {
/**
* 查詢所有使用者資訊
* @return
*/
@Select("select * from user")
@Results(id = "userMap", value = {
@Result(id = true, property = "id", column = "id"),
@Result(property = "username", column = "username"),
@Result(property = "birthday", column = "birthday"),
@Result(property = "address", column = "address"),
@Result(property = "sex", column = "sex"),
@Result(property = "accounts", column = "id", many = @Many(select = "com.hooware.dao.IAccountDao.findByUid", fetchType = FetchType.LAZY))
})
List<User> findAll();
/**
* 根據id查詢使用者
* @param id
* @return
*/
@Select("select * from user where id = #{id}")
@ResultMap("userMap")
User findById(int id);
}