HTML DOM TableHeader abbr 屬性
1 簡介
1.1 什麼是MyBatis
- 持久層框架
- 定製化SQL、儲存過程和高階對映
- 使用簡單的XML或註解配置,對映原生型別、介面和POJO為資料庫中的記錄
1.2 持久化
資料持久化
- 持久化就是將程式的資料在持久狀態和瞬時狀態轉化的過程
- 資料庫、IO檔案持久化
1.3 持久層
Dao層
- 完成持久化工作的程式碼塊
1.4 為什麼需要MyBatis
- 傳統的JDBC程式碼太複雜,需要簡化
- 解除sql和程式程式碼的耦合
- ……
2 第一個MyBatis程式
搭建環境-->匯入MyBatis-->編寫程式碼-->測試
2.1 搭建環境
- 建立資料庫
- 建立專案
- 匯入JDBC驅動,MyBatis包
- 配置mybatis的核心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> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <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> <!--每個Mapper.xml都要在核心配置檔案中註冊--> <mappers> <mapper resource="com/hjc/dao/UserMapper.xml"/> </mappers> </configuration>
- 從xml檔案中構建SqlSessionFactory,我們把構建SqlSessionFactory的方法放在工具類的靜態方法內
public class MyBatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static sqlSession getSqlSession() { return sqlSessionFactory.openSession(); } }
2.2 編寫程式碼
- 編寫實體類(pojo)
- 編寫Dao介面
public interface UserDao {
List<User> getUserList();
}
- 編寫UserMapper.xml檔案(介面實現類由原來的UserDaoImpl轉變為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">
<!--namespace = 繫結一個對應的Dao/Mapper介面-->
<mapper namespace="com.hjc.dao.UserDao">
<!--select查詢語句-->
<select id="getUserList" resultType="com.hjc.pojo.User">
select * from user;
</select>
</mapper>
-
在核心配置檔案中註冊UserMapper.xml
<!--每個Mapper.xml都要在核心配置檔案中註冊--> <mappers> <mapper resource="com/hjc/dao/UserMapper.xml"/> </mappers>
- 在預設的Maven配置下,UserMapper.xml檔案應該放在resources資料夾下,也就是resources/com/hjc/dao/UserMapper.xml路徑
- 要想把UserDao和UserMapper.xml檔案放在一起,需要更改Maven構建配置
- 如果不這樣做,在構建時,Mapper檔案無法被打包到target資料夾中
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
2.3 測試程式碼
public class UserDaoTest {
@Test
public void testGetUserList() {
//獲得SqlSession物件
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//方式一:getMapper
UserDao userDao = sqlSession.getMapper(UserDao.class);
List<User> userList = userDao.getUserList();
//方式二:不建議使用
//List<User> userList = sqlSession.selectList("com.hjc.dao.UserDao.getUserList");
for (User user : userList)
System.out.println(user);
//關閉SqlSession
sqlSession.close();
}
}
3 CRUD
3.1 namespace
namespace中的包名要和Dao/Mapper介面的包名一致
3.2 select
查詢語句
- id:就是對應的namespace中的方法名
- resultType:sql語句執行的返回型別
- parameterType:sql語句的引數型別
- 編寫介面
//根據id查詢使用者
User getUserById(int id);
- 編寫對應Mapper.xml檔案中的sql語句
<select id="getUserById" parameterType="Integer" resultType="com.hjc.pojo.User">
select * from user where id = #{id}
</selects>
- 測試
@Test
public void testGetUserById() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = userDao.getUserById(1);
System.out.println(user);
sqlSession.close();
}
3.3 insert
- 編寫介面
//插入使用者
void addUser(User user);
- 編寫對應Mapper.xml檔案中的sql語句
<insert id="addUser" parameterType="com.hjc.pojo.User">
insert into user (id, name, pwd) values (#{id}, #{name}, #{pwd})
</insert>
- 測試
@Test
public void testAddUser() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
userDao.addUser(new User(5, "test", 123456));
//需要提交事務
sqlSession.commit();
sqlSession.close();
}
3.4 update
- 編寫介面
//更新使用者
void updateUse(User user);
- 編寫對應Mapper.xml檔案中的sql語句
<update id="updateUser" parameterType="com.hjc.pojp.User">
update user set name = #{name}, pwd = #{pwd} where id = #{id}
</update>
- 測試
@Test
public void testUpdateUser() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
userDao.updateUser(new User(5, "test", 123456));
//需要提交事務
sqlSession.commit();
sqlSession.close();
}
3.5 delete
- 編寫介面
//刪除使用者
void deleteUserById(int id);
- 編寫對應Mapper.xml檔案中的sql語句
<delete id="deleteUserById" parameter="Integer">
delete from user where id = #{id}
</delete>
- 測試
@Test
public void testDeleteUserById() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
userDao.deleteUserById(5);
//需要提交事務
sqlSession.commit();
sqlSession.close();
}
注意點:MyBatis預設不會自動提交事務,需要手動提交事務
3.6 Map
如果實體類或資料庫中表的欄位或引數過多,我們應當考慮使用Map作為sql的引數
void addUserByMap(Map<String, Object> map);
<insert id="addUserByMap" parameterType="Map">
insert into user (id, pwd) values (#{userId}, #{userPwd})
</insert>
@Test
public void testAddUserByMap() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
Map<String, Object> map = new HashMap<>();
map.put("userId", 6);
map.put("userPwd", "123456");
userDao.addUserByMap(map);
//需要提交事務
sqlSession.commit();
sqlSession.close();
}
- Map傳遞引數,直接在sql中取出key即可
- 物件傳遞引數,直接在sql中取物件的屬性即可
- 只有一個基本型別引數的情況下,可以直接在sql中取到
- 多個引數的情況,用Map,或者註解
3.7 模糊查詢
List<User> getUserLike(String name);
<select id="getUserLike" parameterType="String" resultType="com.hjc.pojo.User">
select * from user where name like #{name}
</select>
@Test
public void testGetUserLike() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
List<User> userList = userDao.getUserLike("%test%");
for (User user : userList)
System.out.println(user);
sqlSession.close();
}
- 在Java程式碼執行的時候,傳遞萬用字元"% %"
List<User> userList = userDao.getUserLike("%test%");
- 在sql拼接中使用萬用字元"% %"
select * from user where name like "%"#{name}"%"
4 配置解析
4.1 核心配置檔案
- mybatis-config.xml
- 配置檔案會影響MyBatis行為的設定和屬性資訊
configuration(配置)
properties(屬性)
settings(設定)
typeAliases(類型別名)
typeHandlers(型別處理器)
objectFactory(物件工廠)
plugins(外掛)
environments(環境配置)
environment(環境變數)
transactionManager(事務管理器)
dataSource(資料來源)
databaseIdProvider(資料庫廠商標識)
mappers(對映器)
4.2 環境配置(environments)
MyBatis可以配置成適應多種環境,但每個SqlSessionFactory例項只能選擇一種環境
MyBatis預設的事務管理器就是JDBC,連線池POOLED
4.3 屬性(properties)
可以通過properties屬性來實現引用配置檔案,屬性可以在Java屬性檔案(properties檔案)中配置,也可以通過properties元素的子元素來傳遞
- 編寫外部配置檔案db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/test_demo?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
username=root
password=123456
- 在核心配置檔案中引入properties檔案
<properties resource="db.properties"/>
- 也可以在核心配置檔案中增加屬性
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="111111"/>
</properties>
- 如果兩個地方都配置了同一欄位,優先使用外部檔案配置的資訊
4.4 類型別名(typeAliases)
- 類型別名是為Java型別設定一個短的名字,為了減少全限定類名的冗餘
<typeAliases>
<typeAlias type="com.hjc.pojo.User" alias="User"/>
</typeAliases>
- 指定一個包名,MyBatis會在包名下面搜尋需要的JavaBean,預設別名就是這個類的類名,首字母小寫,也可以配合註解@Alias使用
<typeAliases>
<package name="com.hjc.pojo"/>
</typeAliases>
- 在實體類比較少的時候,使用第一種方式;實體類比較多,建議第二種
4.5 設定(settings)
- cacheEnabled:全域性開啟或關閉所有對映器配置檔案中已配置的任何快取
- lazyLoadingEnabled:開啟或關閉延遲載入
- logImpl:指定MyBatis所有日誌的具體實現
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="logImpl" value="LOG4J"/>
</settings>
4.6 對映器(mappers)
註冊繫結Mapper檔案
- 方式一
<mappers>
<mapper resource="com/hjc/dao/UserMapper.xml"/>
</mappers>
- 方式二:使用class檔案繫結註冊
<mappers>
<mapper class="com.hjc.dao.UserMapper"/>
</mappers>
注意點
- 介面和其Mapper配置檔案必須同名
- 介面和其Mapper配置檔案必須在同一個包下
- 方式三:使用掃描包進行繫結註冊
<mappers>
<mapper package="com.hjc.dao"/>
</mappers>
注意點
- 介面和其Mapper配置檔案必須同名
- 介面和其Mapper配置檔案必須在同一個包下
4.7 其他配置
- typeHandlers(型別處理器)
- objectFactory(物件工廠)
- plugins外掛
- mybatis-generator-core
- mybatis-plus
- 通用mapper
4.8 生命週期和作用域
生命週期和作用域至關重要,錯誤使用會導致併發問題
SqlSessionFactoryBuilder
- 一旦建立了SqlSessionFactory,就不再需要SqlSessionFactoryBuilder了
- 區域性變數
SqlSessionFactory
- 相當於資料庫連線池
- 一旦被建立就在應用的執行期間一直存在,沒有理由丟棄它或重新建立另一個例項
- SqlSessionFactory的最佳作用域是應用作用域
- 使用單例模式或者靜態單例模式
SqlSesison
- 相當於連線到連線池的一個請求
- 不是執行緒安全的,不能被共享
- SqlSession的最佳作用域是請求或方法作用域
- 用完之後要立即關閉,否則資源被佔用
圖中的每個Mapper代表一個具體的業務
5 實體類屬性名和資料庫欄位名不一致問題
解決方法:
5.1 起別名
原來是
<select id="getUserById" parameterType="int" resultType="com.hjc.pojo.User">
select * from user where id = #{id}
</select>
改為
<select id="getUserById" parameterType="int" resultType="com.hjc.pojo.User">
select id, name, pwd from user where id = #{id}
</select>
5.2 使用resultMap
結果集對映
<!--結果集對映-->
<resultMap id="UserMap" type="User">
<!--column為資料庫欄位,property為實體類屬性-->
<!--<result column="id" property="id"/>-->
<!--<result column="name" property="name"/>-->
<result column="pwd" property="password"/>
</resultMap>
<select id="getUserById" parameterType="int" resultMap="UserMap">
select * from user where id = #{id}
</select>
對於簡單的語句不需要配置顯示的結果集對映,而對於複雜一點的語句需要描述它們的關係
6 日誌
6.1 日誌工廠
如果資料庫操作出現異常,需要排錯,那麼日誌就是最好的助手
以前可以使用sout列印,debug,現在有日誌工廠
MyBatis使用內建的日誌工廠提供日誌功能,可以設定具體日誌實現
STDOUT_LOGGING
在mybatis核心配置檔案中配置日誌
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
可以直接使用
6.2 LOG4J
-
LOG4J可以控制日誌資訊輸送的目的地是控制檯,檔案,GUI元件
-
可以控制每一條日誌的輸出格式
-
可以定義每一條日誌資訊的級別
-
LOG4J在配置完後不能直接使用
- 先匯入LOG4J的包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- 在resource資料夾下建立log4j.properties檔案
#將等級為DEBUG的日誌資訊輸出到console和file這兩個目的地,console和file的定義在下面的程式碼
log4j.rootLogger=DEBUG,console,file
#控制檯輸出的相關設定
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#檔案輸出的相關設定
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/hjc.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日誌輸出級別
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
- 配置log4j為日誌實現
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
- log4j的使用,測試執行
簡單使用
- 在要使用log4j的類中,匯入包 import org.apache.log4j.Logger;
- 日誌物件,引數為當前類的class
private static Logger logger = Logger.getLogger(UserDaoTest.class);
- 日誌級別
logger.info("info: 開始測試");
logger.debug("debug: 開始debug");
logger.error("error: 出現了錯誤");
7 分頁
如果一次查詢得到的資料量太大,會產生資源浪費的後果,所以要用到分頁
- 減少資料的處理量
7.1 使用limit分頁
語法:select * from user limit startIndex, pageSize;
例子:select * from user limit 1, 3;
MyBatis中實現分頁
- 編寫介面,傳輸引數為map,map記憶體放start Index和pageSize
//分頁查詢
List<User> getUserByLimit(Map<String, Integer> map);
- 編寫對應的Mapper.xml檔案
<select id="getUserByLimit" parameterType="map" resultType="com.hjc.pojo.User">
select * from user limit #{startIndex}, #{pageSize}
</select>
- 測試
@Test
public void testGetUserByLimit() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
Map<String, Integer> map = new HashMap<>();
map.put("startIndex", 1);
map.put("pageSize", 2);
List<User> userList = userDao.getUserByLimit(map);
for (User user : userList)
System.out.println(user);
sqlSession.close();
}
7.2 使用RowBounds分頁
不在sql中使用limit,使用面向物件思想
- 編寫介面
List<user> getUserByRowBounds();
- 編寫對應的Mapper.xml檔案
<select id="getUserByRowBounds" resultType="com.hjc.pojo.User">
select * from user
</select>
- 測試
@Test
public void testGetUserByRowBounds() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
RowBounds rowBounds = new RowBounds(1, 2);
//不使用Mapper來查詢,使用以前的selectList來查詢
List<User> userList =
sqlSession.selectList("com.hjc.dao.UserDao.getUserByRowBounds", null, rowBounds);
for (User user : userList)
System.out.println(user);
sqlSession.close();
}
7.3 使用分頁外掛
MyBatis分頁外掛PageHelper,瞭解
8 使用註解
8.1 面向介面程式設計
在開發中,會選擇面向介面程式設計
- 根本原因是解耦,可拓展,提高複用
- 分層開發中,上層不需要管具體的實現
- 規範性更好
關於介面的理解
- 從深層次的理解,介面是定義(規範、約束)與實現的分離
- 介面的本身反映了系統設計人員對系統的抽象理解
- 介面有兩類
- 第一類是對一個個體的抽象,對應為一個抽象體(abstract class)
- 第二類是對一個個體某一方面的抽象,即形成一個抽象面(interface)
- 一個個體可能有多個抽象面
8.2 註解
- 註解在介面內方法名上標註
@Select("select * from user")
List<User> getUsers();
- 需要在核心配置檔案中繫結介面
<mappers>
<mapper class="com.hjc.dao.UserDao"/>
</mappers>
- 測試
- 本質:反射機制實現
- 底層:動態代理
8.3 註解實現CRUD
可以在工具類內實現自動提交事務
public static sqlSession getSqlSession() {
return sqlSessionFactory.openSession(true);
}
- 編寫介面
public interface UserDao {
@Select("select * from user where id = #{id}")
User getUserById(@param("id") int id);
@Insert("insert into user (id, name, pwd) values (#{id}, #{name}, #{pwd})")
int addUser(User user);
@Update("update user set name = #{name}, pwd = #{pwd} where id = #{id}")
int updateUser(User user);
@Delete("delete from user where id = #{uid}")
int deleteUser(@Param("uid") int id);
}
- 繫結介面
<mappers>
<mapper class="com.hjc.dao.UserDao"/>
</mappers>
- 測試
關於@Param註解
- 基本型別的引數或者String型別,需要加上
- 引用型別不需要加
- 如果只有一個基本型別引數的話,可以忽略
- 在sql語句中使用的引數就是註解內設定的屬性名
9 MyBatis執行流程
10 Lombok
lombok是一個外掛,通過註解來消除業務中冗長的程式碼,尤其對於POJO
使用步驟
- 在IDEA中安裝Lombok外掛
- 在專案中匯入Lombok包
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
- 在實體類上加註解
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- ……
11 多對一關係處理
mysql中多對一查詢方式
- 子查詢
- 聯表查詢
假設多個學生對應一個老師,那麼先分別寫對應實體類
@Data
public Student implements Serializable {
private int id;
private String name;
//學生需要關聯一個老師
private Teacher teacher;
}
@Data
public Teacher implements Serializable {
private int id;
private String name;
}
要查出所有學生以及對應的老師,有兩種方法
11.1 按照查詢巢狀處理
思路:
- 查詢所有的學生資訊
- 根據查詢出的學生tid,查詢對應的老師,相當於子查詢
<select id="getStudent" resultMap="StudentPlusTeacher">
select * from student
</select>
<resultMap id="StudentPlusTeacher" type="Student">
<!--主鍵-->
<id property="id" column="id"/>
<result property="name" column="name"/>
<!--複雜的屬性需要單獨處理,物件用association,集合用collection
javaType表示屬性的型別-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" parameterType="int" resultType="Teacher">
select * form teacher where id = #{id}
</select>
注:在核心配置檔案中已經配置了別名,所以可以省去實體類的包路徑
11.2 按照結果巢狀處理
<select id="getStudent" resultMap="StudentPlusTeacher">
select s.id as sid, s.name as sname, t.name as tname
from student s, teacher t
where s.tid = t.id
</select>
<resultMap id="StudentPlusTeacher" type="Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
12 一對多關係處理
假設一個老師有多個學生,那麼先分別寫對應的實體類
@Data
public class Student implements Serializable {
private int id;
private String name;
private int tid;
}
@Data
public class Teacher implements Serializable {
private int id;
private String name;
//一個老師有多個學生
private List<Student> students;
}
要查出指定老師及其對應的所有學生
12.1 按照結果巢狀處理
<select id="getTeacher" parameterType="int" resultMap="TeacherPlusStudent">
select s.id as sid, s.name as sname, t.name as tname, t.id as tid
from student s, teacher t
where s.tid = t.id and t.id = #{tid}
</select>
<resultMap id="TeacherPlusStudent" type="Teacher">
<id property="id" column="tid"/>
<result property="name" column="tname"/>
<!--複雜的屬性需要單獨處理,物件用association,集合用collection
ofType表示集合中的泛型資訊-->
<collection proerty="students" ofType="Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
12.2 按照查詢巢狀處理
<select id="getTeacher" parameterType="int" resultMap="TeacherPlusStudent">
select * from teacher where id = #{tid}
</select>
<resultMap id="TeacherPlusStudent" type="Teacher">
<collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/>
</resultMap>
<select id="getStudentByTeacherId" parameter="int" resultType="Student">
select * from student where tid = #{tid}
</select>
12.3 總結
- 關聯:association,表示多對一
- 集合:collection,表示一對多
- javaType:用來指定實體類中屬性的型別
- ofType:用來指定集合中的泛型資訊,即集合內的pojo型別
13 動態SQL
動態SQL是指根據不同的條件生成不同的SQL語句,之前的專案寫sql時,有時候會根據不同的條件拼接sql語句,現在可以使用動態SQL來實現
參考mybatis官方文件中對於動態SQL的解釋
13.1 if
<select id="findActiveBlogWithTitleLike" resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
13.2 choose、when、otherwise
相當於switch語句
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
13.3 where、set、trim
上面幾個例子,sql語句中WHERE關鍵字後面都跟了預設的條件state = ‘ACTIVE’,如果把這條語句刪了或者改為動態SQL,那麼整個SQL語句會出現問題,比如
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG
WHERE
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
如果if條件一個都不滿足,那麼sql變為
SELECT * FROM BLOG WHERE
很明顯,這條sql語句是錯誤的
如果第一個id條件不滿足,那麼sql變為
SELECT * FROM BLOG WHERE AND title like #{title} ……
很明顯,這條sql語句也是錯誤的
那麼,MyBatis提供了where標籤,可以解決這些問題。我們把上面的例子用where標籤修改
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG
<where>
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</where>
</select>
where 元素只會在子元素返回任何內容的情況下才插入 “WHERE” 子句。而且,若子句的開頭為 “AND” 或 “OR”,where 元素也會將它們去除
另外,set標籤類似於where標籤,會動態地設定SET關鍵字,同時也會刪掉無關的逗號
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">
username = #{username},
</if>
<if test="password != null">
password = #{password},
</if>
<if test="email != null">
email = #{email},
</if>
<if test="bio != null">
bio = #{bio}
</if>
</set>
where id = #{id}
</update>
set會自動在行首插入SET關鍵字,並根據傳進來的引數是否為空決定sql語句的結構
trim標籤與set標籤等價
13.4 SQL片段
SQL片段:將sql語句的部分提取出來,方便複用
<sql id="test">
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</sql>
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG
<where>
<include refid="test"/>
</where>
</select>
注意:
- 最好基於單表來定義SQL片段
- SQL片段內不要存在where標籤
13.5 foreach
遇到IN語句的時候,可以使用foreach標籤對集合進行遍歷
<select id="selectPostIn" parameterType="list" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<!--傳入引數為list集合-->
<foreach item="item" index="index" collection="list" open="(" separator="," close=")">
#{item}
</foreach>
</select>
可以將任何可迭代物件(如 List、Set 等)、Map 物件或者陣列物件作為集合引數傳遞給 foreach。當使用可迭代物件或者陣列時,index 是當前迭代的序號,item 的值是本次迭代獲取到的元素。當使用 Map 物件(或者 Map.Entry 物件的集合)時,index 是鍵,item 是值
總結
動態SQL就是在拼接SQL語句,只要保證SQL的正確性,就按照SQL的格式排列組合。先寫出完整的SQL語句,再對應修改稱為動態SQL實現通用
14 快取
14.1 簡介
- 什麼是快取
- 存在記憶體中的臨時資料
- 將使用者經常查詢的資料放在快取中,下次查詢時不用再從資料庫中獲取,而是從快取中獲取,從而提高查詢效率,解決高併發系統的效能問題
- 為什麼使用快取
- 減少和資料庫的互動次數,減少系統開銷,提高系統效率
- 什麼資料能使用快取
- 經常查詢且不經常改變的資料
14.2 MyBatis快取
- MyBatis包含一個強大的查詢快取特性,可以非常方便地定製和配置快取
- MyBatis預設定義了兩級快取:一級快取和二級快取
- 預設情況下,只有一級快取開啟(SqlSession級別的快取,也稱為本地快取)
- 二級快取需要手動開啟和配置,是基於namespace級別的快取
- MyBatis定義了快取介面Cache,可以通過實現Cache介面來自定義二級快取
14.3 快取原理
14.4 一級快取
- 一級快取也叫本地快取:SqlSession
- 相當於一個map
- 和資料庫同一次會話(同一次SqlSession)期間查詢到的資料會放在本地快取中
- 以後如果要獲取相同的資料,可以直接從快取中取,不需要從資料庫中查詢
測試
@Test
public void test() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user1 = userDao.getUserById(1);
User user2 = userDao.getUserById(1);
System.out.println(user1 == user2);
sqlSession.close();
}
根據日誌,我們可以看到sql只執行了一次,user1和user2是同一個
- 快取失效的情況
- 查詢不同的資料
- 增刪改操作可能會改變原來的資料,快取會重新整理
- 使用不同的Mapper進行查詢
- 手動清除快取
14.5 二級快取
- 二級快取也叫全域性快取,作用域比一級快取要大
- 基於namespace級別的快取,一個名稱空間對應一個二級快取
- 工作機制
- 一次會話查詢一條資料,這個資料會被放在當前會話的一級快取中
- 如果當前會話關閉了,那麼對應的一級快取就沒了。但是二級快取開啟後,會話關閉之後,一級快取中的資料會儲存到二級快取中
- 新的會話查詢資料,就可以從二級快取中獲取資料
- 不同的Mapper查出的資料會放在對應的快取中
步驟
- 在核心配置檔案中開啟全域性快取
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
- 在要使用二級快取的Mapper.xml中開啟二級快取,可以自定義快取屬性
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
- 測試
@Test
public void test() {
SqlSession sqlSession1 = MyBatisUtils.getSqlSession();
UserDao userDao1 = sqlSession1.getMapper(UserDao.class);
User user1 = userDao1.getUserById(1);
sqlSession1.close();
//只有sqlSession1關閉後,一級快取內的資料才會進入二級快取
SqlSession sqlSession2 = MyBatisUtils.getSqlSession();
UserDao userDao2 = sqlSession2.getMapper(UserDao.class);
User user2 = userDao2.getUserById(1);
sqlSession2.close();
System.out.println(user1 == user2);
}
根據日誌可以看到sql只執行了一次,且查到的User物件是同一個
注意:
- 二級快取對同一個Mapper下的資料才能起到提高效率的效果
- 資料會先放在一級快取中,只有當會話提交或者關閉後,才會放到二級快取中
14.6 自定義快取EhCache
可以使用自定義的快取,也可以使用第三方的快取