1. 程式人生 > 實用技巧 >HTML DOM TableHeader abbr 屬性

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 搭建環境

  1. 建立資料庫
  2. 建立專案
  3. 匯入JDBC驅動,MyBatis包
  4. 配置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>
  1. 從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 編寫程式碼

  1. 編寫實體類(pojo)
  2. 編寫Dao介面
public interface UserDao {
    List<User> getUserList();
}
  1. 編寫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>
  1. 在核心配置檔案中註冊UserMapper.xml

    <!--每個Mapper.xml都要在核心配置檔案中註冊-->
    <mappers>
      <mapper resource="com/hjc/dao/UserMapper.xml"/>
    </mappers>
    
    1. 在預設的Maven配置下,UserMapper.xml檔案應該放在resources資料夾下,也就是resources/com/hjc/dao/UserMapper.xml路徑
    2. 要想把UserDao和UserMapper.xml檔案放在一起,需要更改Maven構建配置
    3. 如果不這樣做,在構建時,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語句的引數型別
  1. 編寫介面
//根據id查詢使用者
User getUserById(int id);
  1. 編寫對應Mapper.xml檔案中的sql語句
<select id="getUserById" parameterType="Integer" resultType="com.hjc.pojo.User">
    select * from user where id = #{id}
</selects>
  1. 測試
@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

  1. 編寫介面
//插入使用者
void addUser(User user);
  1. 編寫對應Mapper.xml檔案中的sql語句
<insert id="addUser" parameterType="com.hjc.pojo.User">
    insert into user (id, name, pwd) values (#{id}, #{name}, #{pwd})
</insert>
  1. 測試
@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

  1. 編寫介面
//更新使用者
void updateUse(User user);
  1. 編寫對應Mapper.xml檔案中的sql語句
<update id="updateUser" parameterType="com.hjc.pojp.User">
    update user set name = #{name}, pwd = #{pwd} where id = #{id}
</update>
  1. 測試
@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

  1. 編寫介面
//刪除使用者
void deleteUserById(int id);
  1. 編寫對應Mapper.xml檔案中的sql語句
<delete id="deleteUserById" parameter="Integer">
    delete from user where id = #{id}
</delete>
  1. 測試
@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元素的子元素來傳遞

  1. 編寫外部配置檔案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
  1. 在核心配置檔案中引入properties檔案
<properties resource="db.properties"/>
  1. 也可以在核心配置檔案中增加屬性
<properties resource="db.properties">
    <property name="username" value="root"/>
    <property name="password" value="111111"/>
</properties>
  1. 如果兩個地方都配置了同一欄位,優先使用外部檔案配置的資訊

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檔案

  1. 方式一
<mappers>
  <mapper resource="com/hjc/dao/UserMapper.xml"/>
</mappers>
  1. 方式二:使用class檔案繫結註冊
<mappers>
    <mapper class="com.hjc.dao.UserMapper"/>
</mappers>

注意點

  • 介面和其Mapper配置檔案必須同名
  • 介面和其Mapper配置檔案必須在同一個包下
  1. 方式三:使用掃描包進行繫結註冊
<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在配置完後不能直接使用

  1. 先匯入LOG4J的包
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

  1. 在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
  1. 配置log4j為日誌實現
<settings>
    <setting name="logImpl" value="LOG4J"/>
</settings>
  1. log4j的使用,測試執行

簡單使用

  1. 在要使用log4j的類中,匯入包 import org.apache.log4j.Logger;
  2. 日誌物件,引數為當前類的class
private static Logger logger = Logger.getLogger(UserDaoTest.class);
  1. 日誌級別
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中實現分頁

  1. 編寫介面,傳輸引數為map,map記憶體放start Index和pageSize
//分頁查詢
List<User> getUserByLimit(Map<String, Integer> map);
  1. 編寫對應的Mapper.xml檔案
<select id="getUserByLimit" parameterType="map" resultType="com.hjc.pojo.User">
    select * from user limit #{startIndex}, #{pageSize}
</select>
  1. 測試
@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,使用面向物件思想

  1. 編寫介面
List<user> getUserByRowBounds();
  1. 編寫對應的Mapper.xml檔案
<select id="getUserByRowBounds" resultType="com.hjc.pojo.User">
    select * from user
</select>
  1. 測試
@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 註解

  1. 註解在介面內方法名上標註
@Select("select * from user")
List<User> getUsers();
  1. 需要在核心配置檔案中繫結介面
<mappers>
    <mapper class="com.hjc.dao.UserDao"/>
</mappers>
  1. 測試
  • 本質:反射機制實現
  • 底層:動態代理

8.3 註解實現CRUD

可以在工具類內實現自動提交事務

public static sqlSession getSqlSession() {
    return sqlSessionFactory.openSession(true);
}
  1. 編寫介面
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);
}
  1. 繫結介面
<mappers>
    <mapper class="com.hjc.dao.UserDao"/>
</mappers>
  1. 測試

關於@Param註解

  • 基本型別的引數或者String型別,需要加上
  • 引用型別不需要加
  • 如果只有一個基本型別引數的話,可以忽略
  • 在sql語句中使用的引數就是註解內設定的屬性名

9 MyBatis執行流程

10 Lombok

lombok是一個外掛,通過註解來消除業務中冗長的程式碼,尤其對於POJO

使用步驟

  1. 在IDEA中安裝Lombok外掛
  2. 在專案中匯入Lombok包
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.10</version>
</dependency>
  1. 在實體類上加註解
    1. @Data
    2. @AllArgsConstructor
    3. @NoArgsConstructor
    4. ……

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 按照查詢巢狀處理

思路:

  1. 查詢所有的學生資訊
  2. 根據查詢出的學生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 總結

  1. 關聯:association,表示多對一
  2. 集合:collection,表示一對多
  3. javaType:用來指定實體類中屬性的型別
  4. 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 簡介

  1. 什麼是快取
    • 存在記憶體中的臨時資料
    • 將使用者經常查詢的資料放在快取中,下次查詢時不用再從資料庫中獲取,而是從快取中獲取,從而提高查詢效率,解決高併發系統的效能問題
  2. 為什麼使用快取
    • 減少和資料庫的互動次數,減少系統開銷,提高系統效率
  3. 什麼資料能使用快取
    • 經常查詢且不經常改變的資料

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查出的資料會放在對應的快取中

步驟

  1. 在核心配置檔案中開啟全域性快取
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
  1. 在要使用二級快取的Mapper.xml中開啟二級快取,可以自定義快取屬性
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
  1. 測試
@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

可以使用自定義的快取,也可以使用第三方的快取