MyBatis的配置和使用原理
MyBatis 是支持定制化 SQL、存儲過程以及高級映射的優秀的持久層框架。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可以對配置和原生Map使用簡單的 XML 或注解,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫 中的記錄。
MyBatis讓程序將主要精力放在sql上,通過MyBatis提供的映射方式,自由靈活生成(半自動化,大部分需要程序員編寫sql)滿足需要sql語句。
MyBatis可以將向 preparedStatement中的輸入參數自動進行輸入映射,將查詢結果集靈活映射成java對象。(輸出映射)
MyBatis框架原理
MyBatis框架執行過程:
1、配置MyBatis的配置文件,SqlMapConfig.xml(名稱不固定)
2、通過配置文件,加載MyBatis運行環境,創建SqlSessionFactory會話工廠
SqlSessionFactory 在實際使用時按單例方式。3、通過SqlSessionFactory創建SqlSession
SqlSession 是一個面向用戶接口(提供操作數據庫方法),實現對象是線程不安全的,建議sqlSession應用場合在方法體內。4、調用 sqlSession 的方法去操作數據。
如果需要提交事務,需要執行 SqlSession 的 commit() 方法。5、釋放資源,關閉SqlSession
MyBatis常見用法
新建一個工程用于描述這些用法。
import java.io.Serializable;
import java.util.Date;
import java.util.List;
public class User implements Serializable {
// 屬性名和數據庫表的字段對應
private int id;
private String username;// 用戶姓名
private String sex;// 性別
private Date birthday;// 生日
private String address;// 地址
// 用戶創建的訂單列表
private List<Orders> ordersList;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", sex=" + sex + ", birthday=" + birthday + ", address="
+ address + "]";
}
public List<Orders> getOrdersList() {
return ordersList;
}
public void setOrdersList(List<Orders> ordersList) {
this.ordersList = ordersList;
}
}
編寫User.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">
<!-- namespace命名空間,作用就是對sql進行分類化管理,理解sql隔離
注意:使用mapper代理方法開發,namespace有特殊重要的作用
-->
<mapper namespace="test">
<!-- 在 映射文件中配置很多sql語句 -->
<!-- 需求:通過id查詢用戶表的記錄 -->
<!-- 通過 select執行數據庫查詢
id:標識 映射文件中的 sql
將sql語句封裝到mappedStatement對象中,所以將id稱為statement的id
parameterType:指定輸入 參數的類型,這里指定int型
#{}表示一個占位符號
#{id}:其中的id表示接收輸入 的參數,參數名稱就是id,如果輸入 參數是簡單類型,#{}中的參數名可以任意,可以value或其它名稱
resultType:指定sql輸出結果 的所映射的java對象類型,select指定resultType表示將單條記錄映射成的java對象。
-->
<select id="findUserById" parameterType="int" resultType="cn.itcast.mybatis.po.User">
SELECT * FROM USER WHERE id=#{value}
</select>
<!-- 根據用戶名稱模糊查詢用戶信息,可能返回多條
resultType:指定就是單條記錄所映射的java對象 類型
${}:表示拼接sql串,將接收到參數的內容不加任何修飾拼接在sql中。
使用${}拼接sql,引起 sql注入
${value}:接收輸入 參數的內容,如果傳入類型是簡單類型,${}中只能使用value
-->
<select id="findUserByName" parameterType="java.lang.String" resultType="cn.itcast.mybatis.po.User">
SELECT * FROM USER WHERE username LIKE '%${value}%'
</select>
<!-- 添加用戶
parameterType:指定輸入 參數類型是pojo(包括 用戶信息)
#{}中指定pojo的屬性名,接收到pojo對象的屬性值,MyBatis通過OGNL獲取對象的屬性值
-->
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<!--
將插入數據的主鍵返回,返回到user對象中
SELECT LAST_INSERT_ID():得到剛insert進去記錄的主鍵值,只適用與自增主鍵
keyProperty:將查詢到主鍵值設置到parameterType指定的對象的哪個屬性
order:SELECT LAST_INSERT_ID()執行順序,相對于insert語句來說它的執行順序
resultType:指定SELECT LAST_INSERT_ID()的結果類型
-->
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
SELECT LAST_INSERT_ID()
</selectKey>
insert into user(username,birthday,sex,address) value(#{username},#{birthday},#{sex},#{address})
<!--
使用MySQL的uuid()生成主鍵
執行過程:
首先通過uuid()得到主鍵,將主鍵設置到user對象的id屬性中
其次在insert執行時,從user對象中取出id屬性值
-->
<!-- <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.String">
SELECT uuid()
</selectKey>
insert into user(id,username,birthday,sex,address) value(#{id},#{username},#{birthday},#{sex},#{address}) -->
</insert>
<!-- 刪除 用戶
根據id刪除用戶,需要輸入 id值
-->
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id=#{id}
</delete>
<!-- 根據id更新用戶
分析:
需要傳入用戶的id
需要傳入用戶的更新信息
parameterType指定user對象,包括 id和更新信息,注意:id必須存在
#{id}:從輸入 user對象中獲取id屬性值
-->
<update id="updateUser" parameterType="cn.itcast.mybatis.po.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address}
where id=#{id}
</update>
</mapper>
配置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>
<!-- 和spring整合后 environments配置將廢除-->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事務管理,事務控制由MyBatis-->
<transactionManager type="JDBC" />
<!-- 數據庫連接池,由MyBatis管理-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<!-- 加載 映射文件 -->
<mappers>
<mapper resource="sqlmap/User.xml"/>
<!--通過resource方法一次加載一個映射文件 -->
<!-- <mapper resource="mapper/UserMapper.xml"/> -->
<!-- 通過mapper接口加載單個 映射文件
遵循一些規范:需要將mapper接口類名和mapper.xml映射文件名稱保持一致,且在一個目錄 中
上邊規范的前提是:使用的是mapper代理方法
-->
<!-- <mapper class="cn.itcast.mybatis.mapper.UserMapper"/> -->
<!-- 批量加載mapper
指定mapper接口的包名,MyBatis自動掃描包下邊所有mapper接口進行加載
遵循一些規范:需要將mapper接口類名和mapper.xml映射文件名稱保持一致,且在一個目錄 中
上邊規范的前提是:使用的是mapper代理方法
-->
<package name="cn.itcast.mybatis.mapper"/>
</mappers>
</configuration>
查找
對應的User.xml中的statement
模糊查找
對應的User.xml中的statement
插入
對應的User.xml中的statement
刪除
對應的User.xml中的statement
修改
對應的User.xml中的statement
parameterType
在映射文件中通過parameterType指定輸入 參數的類型。
對于綜合查詢,建議parameterType使用包裝的pojo,(將各種pojo包裝在一個單獨的類)有利于系統 擴展。
resultType
在映射文件中通過resultType指定輸出結果的類型。查詢到的列名和resultType指定的pojo的屬性名一致,才能映射成功。
reusltMap
可以通過resultMap 完成一些高級映射。
如果查詢到的列名和映射的pojo的屬性名不一致時,通過resultMap設置列名和屬性名之間的對應關系(映射關系)。可以完成映射。
高級映射:
將關聯查詢的列映射到一個pojo屬性中。(一對一)
將關聯查詢的列映射到一個List<pojo>中。(一對多)
#{}和${}
selectOne和selectList
selectOne表示查詢出一條記錄進行映射。如果使用selectOne可以實現使用selectList也可以實現(list中只有一個對象)。
selectList表示查詢出一個列表(多條記錄)進行映射。如果使用selectList查詢多條記錄,不能使用selectOne。
如果使用selectOne會報錯:
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 4
MyBatis和Hibernate本質區別和應用場景
hibernate :是一個標準ORM框架(對象關系映射)。入門門檻較高的,不需要程序寫sql,sql語句自動生成了。
對sql語句進行優化、修改比較困難的。
應用場景:適用與需求變化不多的中小型項目,比如:后臺管理系統,erp、orm、oa。MyBatis:專注是sql本身,需要程序員自己編寫sql語句,sql修改、優化比較方便。MyBatis是一個不完全 的ORM框架,雖然程序員自己寫sql,MyBatis 也可以實現映射(輸入映射、輸出映射)。
應用場景:
適用與需求變化較多的項目,比如:互聯網項目。
企業進行技術選型,以低成本高回報作為技術選型的原則,根據項目組的技術力量進行選擇。
MyBatis開發dao的方法:
(1)、原始dao 的方法
需要程序員編寫dao接口和實現類
需要在dao實現類中注入一個SqlSessionFactory工廠。
(2)、mapper代理開發方法(建議使用)
只需要程序員編寫mapper接口(就是dao接口)
程序員在編寫mapper.xml(映射文件)和mapper.java需要遵循一個開發規范:
1、mapper.xml中namespace就是mapper.java的類全路徑。
2、mapper.xml中statement的id和mapper.java中方法名一致。
3、mapper.xml中statement的parameterType指定輸入參數的類型和mapper.java的方法輸入 參數類型一致。
4、mapper.xml中statement的resultType指定輸出結果的類型和mapper.java的方法返回值類型一致。
查詢緩存
MyBatis提供查詢緩存,用于減輕數據壓力,提高數據庫性能。
MyBatis提供一級緩存,和二級緩存。
數據結構 (HashMap)用于存儲緩存數據。不同的sqlSession之間的緩存數據區域(HashMap)是互相不影響的。
二級緩存是mapper級別的緩存,多個SqlSession去操作同一個Mapper的sql語句,多個SqlSession可以共用二級緩存,二級緩存是跨SqlSession的。
為什么要用緩存?
如果緩存中有數據就不用從數據庫中獲取,大大提高系統性能。
- 一級緩存工作原理(SqlSession級別)
spring 進行整合開發,事務控制在service中。
一個service方法中包括 很多mapper方法調用。
如果是執行兩次service調用查詢相同 的用戶信息,不走一級緩存,因為session方法結束,sqlSession就關閉,一級緩存就清空。
- 二級緩存工作原理(mapper級別)
首先開啟MyBatis的二級緩存。
sqlSession1去查詢用戶id為1的用戶信息,查詢到用戶信息會將查詢數據存儲到二級緩存中。
如果SqlSession3去執行相同 mapper下sql,執行commit提交,清空該 mapper下的二級緩存區域的數據。
sqlSession2去查詢用戶id為1的用戶信息,去緩存中找是否存在數據,如果存在直接從緩存中取出數據。
- 二級緩存與一級緩存區別
二級緩存的范圍更大,多個sqlSession可以共享一個UserMapper的二級緩存區域。
UserMapper有一個二級緩存區域(按namespace分) ,其它mapper也有自己的二級緩存區域(按namespace分)。
每一個namespace的mapper都有一個二級緩存區域,兩個mapper的namespace如果相同,這兩個mapper執行sql查詢到數據將存在相同 的二級緩存區域中。
- useCache配置
在statement中設置useCache=false可以禁用當前select語句的二級緩存,即每次查詢都會發出sql去查詢,默認情況是true,即該sql使用二級緩存。
總結:針對每次查詢都需要最新的數據sql,要設置成useCache=false,禁用二級緩存。
- 刷新緩存(就是清空緩存)
在mapper的同一個namespace中,如果有其它insert、update、delete操作數據后需要刷新緩存,如果不執行刷新緩存會出現臟讀。
設置statement配置中的flushCache=”true” 屬性,默認情況下為true即刷新緩存,如果改成false則不會刷新。使用緩存時如果手動修改數據庫表中的查詢數據會出現臟讀。
如下:
總結:一般下執行完commit操作都需要刷新緩存,flushCache=true表示刷新緩存,這樣可以避免數據庫臟讀。
分布式緩存框架Ehcache
Ehcache是一個分布式緩存框架。(現在僅介紹單機應用在MyBatis,分布式應用的研究后續補上)
我們系統為了提高系統并發,性能、一般對系統進行分布式部署(集群部署方式)
不使用分布緩存,緩存的數據在各各服務單獨存儲,不方便系統 開發。所以要使用分布式緩存對緩存數據進行集中管理。
MyBatis無法實現分布式緩存,需要和其它分布式緩存框架進行整合。
MyBatis提供了一個cache接口,如果要實現自己的緩存邏輯,實現cache接口開發即可。
MyBatis和ehcache整合,MyBatis和ehcache整合包中提供了一個cache接口的實現類。
Ehcache與MyBatis整合
配置mapper中cache中的type為ehcache對cache接口的實現類型。
二級緩存應用場景
對于訪問多的查詢請求且用戶對查詢結果實時性要求不高,此時可采用MyBatis二級緩存技術降低數據庫訪問量,提高訪問速度,業務場景比如:耗時較高的統計分析sql、電話賬單查詢sql等。
實現方法如下:通過設置刷新間隔時間,由MyBatis每隔一段時間自動清空緩存,根據數據變化頻率設置緩存刷新間隔flushInterval,比如設置為30分鐘、60分鐘、24小時等,根據需求而定。
局限性 :MyBatis二級緩存對細粒度的數據級別的緩存實現不好,比如如下需求:對商品信息進行緩存,由于商品信息查詢訪問量大,但是要求用戶每次都能查詢最新的商品信息,此時如果使用MyBatis的二級緩存就無法實現當一個商品變化時只刷新該商品的緩存信息而不刷新其它商品的信息,因為MyBatis的二級緩存區域以mapper為單位劃分,當一個商品信息變化會將所有商品信息的緩存數據全部清空。解決此類問題需要在業務層根據需求對數據有針對性緩存。
逆向工程
MyBatis需要程序員自己編寫sql語句,MyBatis官方提供逆向工程 可以針對單表自動生成MyBatis執行所需要的代碼(mapper.java,mapper.xml、po..)
企業實際開發中,常用的逆向工程方式:
由數據庫的表生成java代碼。
代碼自動生成配置文件。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="testTables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自動生成的注釋 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--數據庫連接的信息:驅動類、連接地址、用戶名、密碼 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root"
password="mysql">
</jdbcConnection>
<!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver"
connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg"
userId="yycg"
password="yycg">
</jdbcConnection> -->
<!-- 默認false,把JDBC DECIMAL 和 NUMERIC 類型解析為 Integer,為 true時把JDBC DECIMAL 和
NUMERIC 類型解析為java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成PO類的位置 -->
<javaModelGenerator targetPackage="cn.itcast.ssm.po"
targetProject=".\src">
<!-- enableSubPackages:是否讓schema作為包的后綴 -->
<property name="enableSubPackages" value="false" />
<!-- 從數據庫返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="cn.itcast.ssm.mapper"
targetProject=".\src">
<!-- enableSubPackages:是否讓schema作為包的后綴 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetPackage:mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="cn.itcast.ssm.mapper"
targetProject=".\src">
<!-- enableSubPackages:是否讓schema作為包的后綴 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定數據庫表 -->
<table tableName="items"></table>
<table tableName="orders"></table>
<table tableName="orderdetail"></table>
<table tableName="user"></table>
<!-- 有些表的字段需要指定java類型
<table schema="" tableName="">
<columnOverride column="" javaType="" />
</table> -->
</context>
</generatorConfiguration>
自動生成代碼的代碼。
打開UserMapper.java,默認生成的Mapper映射接口就是下面這么多。