1. 程式人生 > 其它 >狂神Mybatis筆記

狂神Mybatis筆記

Mybatis

所有程式碼已上傳Gitee

https://gitee.com/deza-to/learn-mybatis

1、簡介

1.1、什麼是Mybatis

  • MyBatis 是一款優秀的持久層框架。
  • 它支援自定義 SQL、儲存過程以及高階對映。
  • MyBatis 免除了幾乎所有的 JDBC 程式碼以及設定引數和獲取結果集的工作。
  • MyBatis 可以通過簡單的 XML 或註解來配置和對映原始型別、介面和 Java POJO(Plain Old Java Objects,普通老式 Java 物件)為資料庫中的記錄。
  • MyBatis 本是apache的一個開源專案iBatis, 2010年這個專案由apache software foundation 遷移到了[google code](
    https://baike.baidu.com/item/google
    code/2346604),並且改名為MyBatis 。2013年11月遷移到Github

如何獲取Mybatis

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.2</version>
</dependency>
  • Github :
  • 中文文件 :

1.2、持久化

資料持久化

  • 持久化就是將程式的資料在持久狀態和瞬時狀態轉換的過程
  • 記憶體:斷電即失
  • 資料庫(jdbc),io檔案持久化

1.3、持久層

Dao層,Service層,Controller層

  • 完成持久化工作的程式碼塊
  • 層界限十分明顯

1.4、為什麼需要Mybatis

  • 方便

  • 傳統的JDBC程式碼太複雜,需要框架來簡化。自動化。

  • 幫助程式設計師將資料存入到資料庫中。

  • 不用Mybatis也可以,但是使用了更容易上手。

  • 優點:

    • 簡單易學
    • 靈活
    • sql和程式碼的分離,提高了可維護性
    • 提供對映標籤,支援物件與資料庫的orm欄位關係對映
    • 提供物件關係對映標籤,支援物件關係組建維護
    • 提供xml標籤,支援編寫動態sql

2、第一個Mybatis程式

思路:搭建環境->匯入Mybatis->編寫程式碼->測試

2.1、搭建環境

新建專案

  1. 新建一個普通的maven專案

  2. 刪除src目錄

  3. 匯入maven依賴

    <dependencies>
        <!--mysql驅動-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.25</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    

2.2、建立一個模組

  • 編寫Mybatis核心配置檔案

    <?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核心配置檔案-->
    <configuration>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
                    <property name="username" value="root"/>
                    <property name="password" value="cqp123"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <mapper resource="org/mybatis/example/BlogMapper.xml"/>
        </mappers>
    </configuration>
    
  • 編寫Mybatis工具類

    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    import java.io.IOException;
    import java.io.InputStream;
    
    public class MybatisUtils {
    
        private static SqlSessionFactory sqlSessionFactory;
    
        static {
            try {
                //使用Mybatis第一步:獲取sqlSessionFactory物件
                String resource = "mybatis-config.xml";
                InputStream inputStream = Resources.getResourceAsStream(resource);
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        //既然有了 SqlSessionFactory,顧名思義,我們可以從中獲得 SqlSession 的例項。SqlSession 提供了在資料庫執行 SQL 命令所需的所有方法。
        //你可以通過 SqlSession 例項來直接執行已對映的 SQL 語句。
        public static SqlSession getSqlSession() {
            return sqlSessionFactory.openSession();
        }
    }
    

2.3、編寫程式碼

  • 實體類

    package com.chen.pojo;
    
    import java.util.Date;
    
    public class User {
        private int id;
        private String name;
        private String pwd;
        private Date createTime;
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getPwd() {
            return pwd;
        }
    
        public void setPwd(String pwd) {
            this.pwd = pwd;
        }
    
        public Date getCreateTime() {
            return createTime;
        }
    
        public void setCreateTime(Date createTime) {
            this.createTime = createTime;
        }
    
        public User() {
        }
    
        public User(int id, String name, String pwd, Date createTime) {
            this.id = id;
            this.name = name;
            this.pwd = pwd;
            this.createTime = createTime;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", pwd='" + pwd + '\'' +
                    ", createTime=" + createTime +
                    '}';
        }
    }
    
    
  • Dao介面

    import com.chen.pojo.User;
    
    import java.util.List;
    
    public interface UserDao {
        List<User> getUserList();
    }
    
  • Dao介面實現類

    <?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.chen.dao.UserDao">
        <select id="getUserList" resultType="com.chen.pojo.User">
            select * from user;
        </select>
    </mapper>
    

2.4、測試

import com.chen.pojo.User;
import com.chen.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserDaoTest {

    @Test
    public void test1(){
        //第一步:獲取sqlSession物件
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //方式一:getMapper
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        List<User> userList = mapper.getUserList();
        userList.stream().forEach(System.out::println);

        //方式二:
        //List<User> userList = sqlSession.selectList("com.chen.dao.UserDao.getUserList");
        
        //關閉sqlSession
        sqlSession.close();
    }
}

可能遇到的問題:

  1. 配置檔案沒有註冊
  2. 繫結介面錯誤
  3. 方法名不對
  4. 返回型別不對
  5. Maven匯出資源問題

3、CRUD

3.1、namespace

namespace中的包名要和介面的包名一致

3.2、select

選擇,查詢語句

  • id:就是對應的namespace中的方法名
  • resultType:sql語句執行的返回值
  • parameterType:引數型別
  1. 編寫介面
//根據id查詢使用者
User getUserById(int id);
  1. 編寫對應的mapper中的sql語句
<select id="getUserById" resultType="com.chen.pojo.User" parameterType="int">
    select * from mybatis.user where id = #{id};
</select>
  1. 測試
@Test
public void testGetUserById(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.getUserById(1);
    System.out.println(user);
    sqlSession.close();
}

3.3、insert

  1. 編寫介面
//插入一個使用者
int addUser(User user);
  1. 編寫對應的mapper中的sql語句
<!--物件中的屬性可以直接取出來-->
<insert id="addUser" parameterType="com.chen.pojo.User">
    insert into mybatis.user (name,pwd,createTime) values(#{name},#{pwd},#{createTime});
</insert>
  1. 測試
@Test
public void testAddUser(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = new User();
    user.setName("李雲龍");
    user.setPwd("123abc");
    user.setCreateTime(new Date());
    int i = mapper.addUser(user);
    sqlSession.commit();
    System.out.println("插入了"+i+"個使用者");
    sqlSession.close();
}

3.4、update

  1. 編寫介面
//修改使用者
int updateUser(User user);
  1. 編寫對應的mapper中的sql語句
<update id="updateUser" parameterType="com.chen.pojo.User">
    update mybatis.user set name=#{name},pwd=#{pwd},createTime=#{createTime} where id=#{id};
</update>
  1. 測試
@Test
public void testUpdateUser(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = new User("楚雲飛","eeeeee",new Date());
    user.setId(3);
    int i = mapper.updateUser(user);
    System.out.println("更新了"+i+"個使用者");
    sqlSession.commit();
    sqlSession.close();
}

3.5、delete

  1. 編寫介面
//刪除使用者
int deleteUser(int id);
  1. 編寫對應的mapper中的sql語句
<delete id="deleteUser" parameterType="int">
    delete from mybatis.user where id=#{id};
</delete>
  1. 測試
@Test
public void testDeleteUser(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int i = mapper.deleteUser(4);
    System.out.println("刪除了"+i+"個使用者");
    sqlSession.commit();
    sqlSession.close();
}

注意點:

增刪改需要提交事務

3.6、分析錯誤

  • 標籤不要匹配錯

  • resource繫結mapper,需要使用路徑

  • 程式配置檔案必須符合規範

  • NullPointException,沒有註冊到資源

  • 輸出的xml檔案中存在中文亂碼問題

  • maven資源沒有匯出=>pom.xml中配置

3.7、萬能的Map方法

假設,我們的實體類或者資料庫中的表,欄位或者引數過多,我們應當考慮使用Map。

//萬能的Map
int addUser2(Map<String,Object> map);
<!--物件中的屬性可以直接取出來  傳遞map中的key-->
<insert id="addUser2" parameterType="map">
    insert into mybatis.user (name,pwd,createTime) values(#{userName},#{userPwd},#{createTime});
</insert>
@Test
public void testAddUser2(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<String, Object> map = new HashMap<>();
    map.put("userName","lemon");
    map.put("userPwd","lemontree");
    map.put("createTime",new Date());
    int i = mapper.addUser2(map);
    sqlSession.commit();
    System.out.println("插入了"+i+"個使用者");
    sqlSession.close();
}

Map傳遞引數,直接在sql中取出key即可。【parameterType="map"】

物件傳遞引數,直接在sql中取物件的屬性即可。【parameterType="Object"】

只有一個基本型別引數的情況下,可以直接在sql中取到。

多個引數用Map,或者註解

3.8、模糊查詢

  1. Java程式碼執行的時候,傳遞萬用字元% %

    List<User> userList = mapper.getUserLike(%李%);
    
  2. 在sql拼接中使用萬用字元

    select * from mybatis.user where name like "%"#{value}"%";
    

4、配置解析

4.1、核心配置檔案

  • mybatis-config.xml

  • MyBatis 的配置檔案包含了會深深影響 MyBatis 行為的設定和屬性資訊。

    configuration(配置)
    properties(屬性)
    settings(設定)
    typeAliases(類型別名)
    typeHandlers(型別處理器)
    objectFactory(物件工廠)
    plugins(外掛)
    environments(環境配置)
    environment(環境變數)
    transactionManager(事務管理器)
    dataSource(資料來源)
    databaseIdProvider(資料庫廠商標識)
    mappers(對映器)
    

4.2、環境配置(environments)

Mybatis可以配置成適應多種環境

不過要記住:儘管可以配置多個環境,但每個 SqlSessionFactory 例項只能選擇一種環境。

<environments default="test">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
            <property name="username" value="root"/>
            <property name="password" value="cqp123"/>
        </dataSource>
    </environment>
    <environment id="test">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
            <property name="username" value="root"/>
            <property name="password" value="cqp123"/>
        </dataSource>
    </environment>
</environments>

Mybatis預設的事務管理器就是JDBC,連線池:POOLED

4.3、屬性(properties)

我們可以通過properties屬性來實現引用配置檔案

編寫一個配置檔案

db.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username=root
password=cqp123

在核心配置檔案中引入

**在xml中所有標籤都有固定的順序 **

<properties resource="db.properties"/>
  • 可以直接引入外部檔案
  • 可以在其中增加一些屬性配置
  • 如果兩個檔案有同一個欄位,優先使用外部配置檔案的

4.4、類型別名(typeAliases)

  • 類型別名可為 Java 型別設定一個縮寫名字。
  • 它僅用於 XML 配置,意在降低冗餘的全限定類名書寫
<!--給實體類起別名-->
<typeAliases>
	<typeAlias type="com.chen.pojo.User" alias="User"/>
</typeAliases>

也可以指定一個包名,Mybatis會在包名下面搜尋需要的Java Bean,

掃描實體類的包,它的預設別名就為這個類的類名,首字母小寫(大寫也可以)

<typeAliases>
    <package name="com.chen.pojo"/>
</typeAliases>

在實體類比較少的時候使用第一種方式,

如果實體類十分多,建議使用第二種。

第一中可以自定義別名,第二種不行,如果非要改,需要在實體類上增加註解

@Alias("User")
public class User {
    private int id;
    private String name;
    private String pwd;
    private Date createTime;
...

4.5、屬性

<setting>
    <!--快取-->
	<setting name="cacheEnabled" value="true"/>
    <!--懶載入-->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!--日誌型別-->
    <setting name="logImpl"/> 
</setting>
logImpl 指定 MyBatis 所用日誌的具體實現,未指定時將自動查詢。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING

4.6、對映器(mappers)

MapperRegistry:註冊繫結我們的Mapper檔案

方式一:【推薦使用】

<!--每一個Mapper.cml都需要在Mybatis核心配置檔案中註冊-->
<mappers>
    <mapper resource="com/chen/dao/UserMapper.xml"/>
</mappers>

方式二:

<mappers>
    <mapper class="com.chen.dao.UserMapper"/>
</mappers>

注意點:

  • 介面和他的Mapper配置檔案必須同名
  • 介面和他的Mapper配置檔案必須在同一個包下

方式三:

使用掃描包進行注入

<mappers>
    <package name="com.chen.dao"/>
</mappers>

注意點:

  • 介面和他的Mapper配置檔案必須同名
  • 介面和他的Mapper配置檔案必須在同一個包下

4.6、生命週期和作用域

生命週期和作用域是至關重要的,因為錯誤的使用會導致非常嚴重的併發問題

SqlSessionFactoryBuilder:

  • 一旦建立了SqlSessionFactory,就不再需要它了。
  • 適合被定義為區域性變數

SqlSessionFactory:

  • 可以想象為資料庫連線池。
  • SqlSessionFactory一旦被建立就應該在應用的執行期間一直存在,沒有任何理由丟棄它或重新建立另一個例項。
  • SqlSeesionFactory的最佳作用域是應用作用域。
  • 最簡單的就是使用單例模式或者靜態單例模式。

SqlSession:

  • 資料庫連線池的一個請求。
  • SqlSession的例項不是執行緒安全的,因此是不能被共享的,所以它的最佳作用域是請求或方法作用域。
  • 用完之後需要趕緊關閉,否則資源被佔用。

這裡每個Mapper就代表每個具體的業務。

5、解決屬性名和欄位名不一致的問題

新建一個專案,拷貝之前的程式碼,測試實體類欄位不一致的情況。

解決方法:起別名

<select id="getUserById" parameterType="int" resultMap="UserMap">
    select id,name,pwd as password,createTime from mybatis.user where id = #{id};
</select>

5.2、resultMap

結果集對映:

<!--結果集對映-->
<resultMap id="UserMap" type="User">
    <result column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="pwd" property="password"/>
    <result column="createTime" property="createTime"/>
</resultMap>

<select id="getUserById" parameterType="int" resultMap="UserMap">
    select id,name,pwd as password,createTime from mybatis.user where id = #{id};
</select>
  • resultMap 元素是 MyBatis 中最重要最強大的元素。
  • ResultMap 的設計思想是,對簡單的語句做到零配置,對於複雜一點的語句,只需要描述語句之間的關係就行了。
  • ResultMap最優秀的地方在於,雖然你已經對他相當瞭解了,但是根本就不需要顯式地用到他們。

6、日誌

6.1、日誌工廠

如果一個數據庫操作出現了異常,我們需要拍錯,此時日誌就是最好的助手。

曾經:sout,debug

現在:日誌工廠

  • SLF4J
  • LOG4J 【掌握】
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING 【掌握】
  • NO_LOGGING

在MyBatis中具體使用哪一個日誌實現,在設定中設定

STDOUT_LOGGING

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

6.2、Log4j

什麼是Log4j?

  • 可以控制日誌資訊輸送的目的地是控制檯、檔案、GUI元件

  • 也可以控制每一條日誌的輸出格式

  • 定義每一條日誌資訊的級別,我們能夠更加細緻地控制日誌的生成過程

  • 可以通過一個配置檔案來靈活地進行配置,而不需要修改應用的程式碼。

  1. 匯入Log4j的包
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
  1. 編寫配置檔案 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/rzp.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.sq1.PreparedStatement=DEBUG
  1. 配置log4j為日誌的實現
<settings>
    <setting name="logImpl" value="LOG4J"/>
</settings>
  1. Log4j的使用
  • 在要使用Log4j的類中,匯入包 import org.apache.log4j.Logger;

  • 日誌物件,引數為當前類的class物件

    Logger logger = Logger.getLogger(UserDaoTest.class);
    
  • logger.info("info: 測試log4j");
    logger.debug("debug: 測試log4j");
    logger.error("error:測試log4j");
    
    1. info

    2. debug

    3. error

7、分頁

思考:為什麼分頁?

  • 減少資料的處理量

7.1 使用Limit分頁

SELECT * from user limit startIndex,pageSize

使用MyBatis實現分頁,核心SQL

  1. 介面
List<User> getUserByLimit(Map<String,Integer> map);
  1. Mapper.xml
<!--分頁查詢-->
<select id="getUserByLimit" resultMap="UserMap" parameterType="map">
    select * from user limit #{startIndex},#{pageSize};
</select>
  1. 測試
@Test
public void testGetUserByLimit(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<String,Integer> map = new HashMap<>();
    map.put("startIndex",1);
    map.put("pageSize",2);
    List<User> userList = mapper.getUserByLimit(map);
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

7.2 RowBounds分頁【不建議在開發中使用,沒有sql分頁方便】

不再使用sql實現分頁

  1. 介面
List<User> getUserByRowBounds();
  1. mapper.xml
<select id="getUserByRowBounds" resultMap="UserMap">
    select * from USER;
</select>
  1. 測試
@Test
public void testGetUserByRowBounds(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    RowBounds rowBounds = new RowBounds(0, 3);
    List<User> userList = sqlSession.selectList("com.chen.dao.UserMapper.getUserByRowBounds", null, rowBounds);
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

7.3、分頁外掛

8、使用註解開發

8.1 面向介面開發

三個面向區別

  • 面向物件是指,我們考慮問題時,以物件為單位,考慮它的屬性和方法;
  • 面向過程是指,我們考慮問題時,以一個具體的流程(事務過程)為單位,考慮它的實現;
  • 介面設計與非介面設計是針對複用技術而言的,與面向物件(過程)不是一個問題,更多的體現就是對系統整體的架構;

8.2 使用註解開發

  1. 註解在介面上實現
@Select("select * from user")
List<User> getUsers();
  1. 需要在核心配置檔案中繫結介面【因為已經不存在*mapper.xml檔案了】
<mappers>
    <mapper class="com.chen.dao.UserMapper"/>
</mappers>
  1. 測試

本質:反射機制

底層:動態代理

8.3、註解CRUD

@Select("select * from user")
List<User> getUsers();

@Insert("insert into user (name,pwd,createTime) values(#{name},#{password},#{createTime})")
int addUser(User user);

@Delete("delete from user where id = #{id}")
int delUser(@Param("id") int id);

@Update("update user set name = #{name},pwd = #{password},createTime = #{createTime} where id = #{id}")
int updUser(User user);

關於@Param( )註解

  • 基本型別的引數或者String型別,需要加上
  • 引用型別不需要加
  • 如果只有一個基本型別的話,可以忽略,但是建議大家都加上
  • 我們在SQL中引用的就是我們這裡的@Param()中設定的屬性名

#{} ${} 區別

相當於PrepareStatement和Statement的區別

{}可以很大程度防止sql注入

9、Lombok

Lombok專案是一個Java庫,它會自動插入編輯器和構建工具中,Lombok提供了一組有用的註釋,用來消除Java類中的大量樣板程式碼。僅五個字元(@Data)就可以替換數百行程式碼從而產生乾淨,簡潔且易於維護的Java類。

使用步驟:

  1. 在IDEA中安裝Lombok外掛

  2. 在專案中匯入lombok的jar包

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.20</version>
    </dependency>
    
  3. 在實體類上加註解

    @Getter and @Setter 新增屬性的get、set方法
    @FieldNameConstants 
    @ToString           重寫toString方法
    @EqualsAndHashCode  在類上重寫equals和hashCode方法
    @AllArgsConstructor 擁有所有屬性的建構函式
    @RequiredArgsConstructor 
    @NoArgsConstructor  沒有任何屬性的建構函式
    @Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
    @Data       
    @Builder
    @SuperBuilder
    @Singular
    @Delegate
    @Value
    @Accessors
    @Wither
    @With
    @SneakyThrows
    @val
    

說明

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String password;
}

10、多對一處理

多對一的理解:

  • 多個學生對應一個老師,即從學生這邊關聯一個老師

10.1、匯入資料

CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老師');

CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8


INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小紅', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小張', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');

實體類

public class Student {
    private int id;
    private String name;
    private Teacher teacher;
}
public class Teacher {
    private int id;
    private String name;
}

10.2、按照查詢巢狀處理

<select id="getStudents" resultMap="StudentTeacher">
    select * from student
</select>
<resultMap id="StudentTeacher" type="Student">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <!--association關聯屬性 property屬性名 javaType屬性型別 column在多的一方的表中的列名-->
    <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<!--這裡傳遞過來的id,只有一個屬性的時候,下面可以寫任何值
    association中column多引數配置:
    column="{key=value,key=value}"
    其實就是鍵值對的形式,key是傳給下個sql的取值名稱,value是片段一中sql查詢的欄位名。
-->
<select id="getTeacher" resultType="Teacher">
    select * from teacher where id = #{tid}
</select>

10.3、按照結果巢狀處理

<select id="getStudents2" resultMap="StudentTeacher2">
    select s.id sid,s.name sname,t.id tid,t.name tname
    from student s,teacher t
    where s.tid=t.id
</select>
<!--結果封裝,將查詢出來的列封裝到物件屬性中-->
<resultMap id="StudentTeacher2" type="Student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <association property="teacher" javaType="Teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
    </association>
</resultMap>

回顧Mysql多對一查詢方式:

  • 子查詢(按照查詢巢狀)
  • 聯表查詢(按照結果巢狀)

11、一對多處理

一對多的理解:

  • 一個老師擁有多個學生
  • 如果對於老師這邊,就是一個一對多的現象,即從一個老師下面擁有一群學生(集合)

實體類

public class Student {

    private int id;
    private String name;
    private int tid;
}
public class Teacher {

    private int id;
    private String name;
    private List<Student> students;
}

按照結果巢狀處理

<select id="getTeacher" resultMap="TeacherStudents">
    select t.id tid,t.name tname,s.id sid,s.name sname
    from teacher t,student s
    where t.id = s.tid and t.id = #{tid}
</select>
<resultMap id="TeacherStudents" type="Teacher">
    <result property="id" column="tid"/>
    <result property="name" column="tname"/>
    <collection property="students" ofType="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="tid" column="tid"/>
    </collection>
</resultMap>

按查詢巢狀處理

<select id="getTeacher2" resultMap="TeacherStudents2">
    select * from teacher where id = #{tid}
</select>
<resultMap id="TeacherStudents2" type="Teacher">
    <!--column是一對多的外來鍵 , 寫的是一的主鍵的列名-->
    <collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudentByTeacherId"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
    select * from student where tid = #{id}
</select>

小結

  1. 關聯 - association【多對一】

  2. 集合 - collection【一對多】

  3. javaType和ofType

    1. javaType用來指定實體類中的型別
    2. ofType用來指定對映到List或者集合中的pojo型別,泛型中的約束型別

注意點

  • 保證SQL的可讀性,儘量保證通俗易懂
  • 注意一對多和多對一,屬性名和欄位的問題
  • 如果問題不好排查錯誤,可以使用日誌,建議使用Log4j

12、動態SQL

什麼是動態SQL:動態SQL就是根據不同的條件生成不同的SQL語句

所謂的動態SQL,本質上還是SQL語句,只是我們可以在SQL層面,去執行一個邏輯程式碼

動態 SQL 是 MyBatis 的強大特性之一。如果你使用過 JDBC 或其它類似的框架,你應該能理解根據不同條件拼接 SQL 語句有多痛苦,例如拼接時要確保不能忘記新增必要的空格,還要注意去掉列表最後一個列名的逗號。利用動態 SQL,可以徹底擺脫這種痛苦。

12.1、搭建環境

CREATE TABLE `mybatis`.`blog`  (
  `id` int(10) NOT NULL AUTO_INCREMENT COMMENT '部落格id',
  `title` varchar(30) NOT NULL COMMENT '部落格標題',
  `author` varchar(30) NOT NULL COMMENT '部落格作者',
  `create_time` datetime(0) NOT NULL COMMENT '建立時間',
  `views` int(30) NOT NULL COMMENT '瀏覽量',
  PRIMARY KEY (`id`)
)
  1. 建立Mybatis基礎工程
  1. IDutil工具類
public class IDUtil {
    public static String getID(){
        return UUID.randomUUID().toString().replaceAll("-","");
    }
}
  1. 實體類編寫
import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.Date;

@Data
@AllArgsConstructor
public class Blog {
    private String id;
    private String title;
    private String author;
    private Date createTime;
    private int views;
}
  1. 編寫Mapper介面及xml檔案
import java.util.List;
import java.util.Map;

public interface BlogMapper {

    int addBlog(Blog blog);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.chen.dao.BlogMapper">
    <insert id="addBlog" parameterType="Blog">
        insert into mybatis.blog (id,title,author,create_time,views)
        values (#{id},#{title},#{author},#{createTime},#{views});
    </insert>
</mapper>

5、mybatis核心配置檔案,下劃線駝峰自動轉換

<?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核心配置檔案-->
<configuration>

    <properties resource="db.properties"/>

    <settings>
        <!--標準的日誌-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <typeAliases>
        <package name="com.chen.pojo"/>
    </typeAliases>

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

    <mappers>
        <package name="com.chen.dao"/>
    </mappers>
    
</configuration>

12.2、if語句

需求:根據作者名字和部落格名字來查詢部落格!如果作者名字為空,那麼只根據部落格名字查詢,反之,則根據作者名來查詢

1、編寫介面類

List<Blog> queryBlogIf(Map map);

2、編寫Mapper.xml

<!--需求1:
根據作者名字和部落格名字來查詢部落格!
如果作者名字為空,那麼只根據部落格名字查詢,反之,則根據作者名來查詢
select * from blog where title = #{title} and author = #{author}
-->
<select id="queryBlogIf" parameterType="map" resultType="Blog">
    select * from blog where 1 = 1
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</select>

3、測試

@Test
public void testQueryBlogIf(){
    SqlSession sqlSession = MybatisUtils.getSqlSession(true);
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Map<String,Object> map = new HashMap<>();
    map.put("title","第一篇部落格");
    map.put("author","chen1");
    List<Blog> blogs = mapper.queryBlogIf(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
    sqlSession.close();
}

12.3、where語句

修改上面的SQL語句

<select id="queryBlogIf" parameterType="map" resultType="Blog">
    select * from blog
    <where>
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </where>
</select>

這個“where”標籤會知道如果它包含的標籤中有返回值的話,它就插入一個‘where’。此外,如果標籤返回的內容是以AND 或OR 開頭的,則它會剔除掉。

12.4、set語句

同理,上面的對於查詢 SQL 語句包含 where 關鍵字,如果在進行更新操作的時候,含有 set 關鍵詞,我們怎麼處理呢?

1、編寫介面方法

int updateBlog(Map map);

2、sql配置檔案

<update id="updateBlog" parameterType="map">
    update blog
    <set>
        <if test="title != null">
            title = #{title},
        </if>
        <if test="author != null">
            author = #{author}
        </if>
    </set>
    where id = #{id}
</update>

3、測試

@Test
public void testUpdateBlog(){
    SqlSession sqlSession = MybatisUtils.getSqlSession(true);
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Map<String,Object> map = new HashMap<>();
    map.put("title","修改後的標題");
    map.put("author","小陳");
    map.put("id","1af1a344e8a4464ab662697c1cc492c5");
    mapper.updateBlog(map);
    sqlSession.close();
}

12.5、choose語句

有時候,我們不想用到所有的查詢條件,只想選擇其中的一個,查詢條件有一個滿足即可,使用 choose 標籤可以解決此類問題,類似於 Java 的 switch 語句

1、編寫介面方法

List<Blog> queryBlogChoose(Map map);

2、sql配置檔案

<select id="queryBlogChoose" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <choose>
            <when test="title != null">
                title = #{title}
            </when>
            <when test="author != null">
                and author = #{author}
            </when>
            <otherwise>
                and views = #{views}
            </otherwise>
        </choose>
    </where>
</select>

3、測試

@Test
public void testQueryBlogChoose(){
    SqlSession sqlSession = MybatisUtils.getSqlSession(true);
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Map<String,Object> map = new HashMap<>();
    //map.put("title","修改後的標題");
    //map.put("author","小陳");
    map.put("views",111);
    List<Blog> blogs = mapper.queryBlogChoose(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
    sqlSession.close();
}

12.6、SQL片段

有的時候,可以將一些通用的sql片段抽取出來方便複用

1、使用SQL標籤抽取通用的部分

<sql id="if-title-author">
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</sql>

2、在需要使用的地方使用include標籤引用

<select id="queryBlogIf" parameterType="map" resultType="Blog">
    select * from blog
    <where>
        <include refid="if-title-author"/>
    </where>
</select>

注意事項:

  • 最好基於單表定義SQL片段
  • 不要存在where標籤

12.7、foreach標籤

1、編寫介面

List<Blog> queryBlogForeach(Map map);

2、編寫SQL語句

<select id="queryBlogForeach" parameterType="map" resultType="Blog">
    select * from blog
    <where>
        <foreach collection="ids" item="id" open="(" close=")" separator="or">
            id = #{id}
        </foreach>
    </where>
</select>

3、測試

@Test
public void testQueryBlogForeach(){
    SqlSession sqlSession = MybatisUtils.getSqlSession(true);
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Map<String,Object> map = new HashMap<>();
    List<String> ids = new ArrayList<>(Arrays.asList("1","2","3"));
    map.put("ids",ids);
    List<Blog> blogs = mapper.queryBlogForeach(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
    sqlSession.close();
}

小結:其實動態 sql 語句的編寫往往就是一個拼接的問題,為了保證拼接準確,我們最好首先要寫原生的 sql 語句出來,然後在通過 mybatis 動態sql 對照著改,防止出錯。多在實踐中使用才是熟練掌握它的技巧。

動態SQL在開發中大量使用,一定要熟練掌握!

13、快取

13.1、簡介

1、什麼是快取 [ Cache ]?

  • 存在記憶體中的臨時資料。
  • 將使用者經常查詢的資料放在快取(記憶體)中,使用者去查詢資料就不用從磁碟上(關係型資料庫資料檔案)查詢,從快取中查詢,從而提高查詢效率,解決了高併發系統的效能問題。

2、為什麼使用快取?

  • 減少和資料庫的互動次數,減少系統開銷,提高系統效率。

3、什麼樣的資料能使用快取?

  • 經常查詢並且不經常改變的資料。

13.2、Mybatis快取

  • MyBatis包含一個非常強大的查詢快取特性,它可以非常方便地定製和配置快取。快取可以極大的提升查詢效率。
  • MyBatis系統中預設定義了兩級快取:一級快取二級快取
    • 預設情況下,只有一級快取開啟。(SqlSession級別的快取,也稱為本地快取)
    • 二級快取需要手動開啟和配置,他是基於namespace級別的快取。
    • 為了提高擴充套件性,MyBatis定義了快取介面Cache。我們可以通過實現Cache介面來自定義二級快取

13.3、一級快取

一級快取也叫本地快取:

  • 與資料庫同一次會話期間查詢到的資料會放在本地快取中。
  • 以後如果需要獲取相同的資料,直接從快取中拿,沒必須再去查詢資料庫;

測試步驟:

  1. 開啟日誌
  2. 測試在一個SqlSession中查詢兩次相同記錄
  3. 檢視日誌輸出

快取失效的情況:

  1. 查詢不同的東西

  2. 增刪改操作,可能會改變原來的資料,所以必定會重新整理快取

  3. 查詢不同的Mapper.xml

  4. 手動清理快取

    sqlSession.clearCache();
    

小結:

一級快取預設是開啟的,只在一次SqlSession中有效,也就是拿到連線到關閉連線這個區間段。

底層就是一個Map

13.4、二級快取

  • 二級快取也叫全域性快取,一級快取作用域太低了,所以誕生了二級快取
  • 基於namespace級別的快取,一個名稱空間,對應一個二級快取;
  • 工作機制
    • 一個會話查詢一條資料,這個資料就會被放在當前會話的一級快取中;
    • 如果當前會話關閉了,這個會話對應的一級快取就沒了;但是我們想要的是,會話關閉了,一級快取中的資料被儲存到二級快取中;
    • 新的會話查詢資訊,就可以從二級快取中獲取內容;
    • 不同的mapper查出的資料會放在自己對應的快取(map)中;

使用快取

1、開啟全域性快取【mybatis-config.xml】

<setting name="cacheEnabled" value="true"/>

2、去每個mapper.xml中配置使用二級快取,這個配置非常簡單;【xxxMapper.xml】

<cache/>

官方示例=====>檢視官方文件
<cache
 eviction="FIFO"
 flushInterval="60000"
 size="512"
 readOnly="true"/>
這個更高階的配置建立了一個 FIFO 快取,每隔 60 秒重新整理,最多可以儲存結果物件或列表的 512 個引用,而且返回的物件被認為是隻讀的,因此對它們進行修改可能會在不同執行緒中的呼叫者產生衝突。

3、測試

問題:我們需要將實體類序列化,否則就會報錯

Caused by: java.io.NotSerializableException:com.chen.pojo.User

小結:

  • 只要開啟了二級快取,在同一個Mapper下就有效
  • 所有資料都會先放在一級快取中
  • 只有當會話提交,或者關閉的時候,才會提交到二級快取中

13.5、快取原理

只有查詢才有快取,根據資料是否需要快取(修改是否頻繁選擇是否開啟) useCache="false"

<select id="getUserByID" resultType="User" useCache="false">
    select * from user where id = #{id}
</select>

13.6、自定義快取-ehcache

Ehcache是一種廣泛使用的開源Java分散式快取。主要面向通用快取

  1. 導包
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>
  1. 在mapper中指定使用我們的ehcache快取實現
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
  1. 編寫ehcache.xml檔案,如果在載入時未找到ehcache.xml資源或出現問題,則將使用預設配置
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
        updateCheck="false">
   <!--
      diskStore:為快取路徑,ehcache分為記憶體和磁碟兩級,此屬性定義磁碟的快取位置。引數解釋如下:
      user.home – 使用者主目錄
      user.dir – 使用者當前工作目錄
      java.io.tmpdir – 預設臨時檔案路徑
    -->
   <diskStore path="./tmpdir/Tmp_EhCache"/>
   
   <defaultCache
           eternal="false"
           maxElementsInMemory="10000"
           overflowToDisk="false"
           diskPersistent="false"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="259200"
           memoryStoreEvictionPolicy="LRU"/>

   <cache
           name="cloud_user"
           eternal="false"
           maxElementsInMemory="5000"
           overflowToDisk="false"
           diskPersistent="false"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="1800"
           memoryStoreEvictionPolicy="LRU"/>
   <!--
      defaultCache:預設快取策略,當ehcache找不到定義的快取時,則使用這個快取策略。只能定義一個。
    -->
   <!--
     name:快取名稱。
     maxElementsInMemory:快取最大數目
     maxElementsOnDisk:硬碟最大快取個數。
     eternal:物件是否永久有效,一但設定了,timeout將不起作用。
     overflowToDisk:是否儲存到磁碟,當系統當機時
     timeToIdleSeconds:設定物件在失效前的允許閒置時間(單位:秒)。僅當eternal=false物件不是永久有效時使用,可選屬性,預設值是0,也就是可閒置時間無窮大。
     timeToLiveSeconds:設定物件在失效前允許存活時間(單位:秒)。最大時間介於建立時間和失效時間之間。僅當eternal=false物件不是永久有效時使用,預設是0.,也就是物件存活時間無窮大。
     diskPersistent:是否快取虛擬機器重啟期資料 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
     diskSpoolBufferSizeMB:這個引數設定DiskStore(磁碟快取)的快取區大小。預設是30MB。每個Cache都應該有自己的一個緩衝區。
     diskExpiryThreadIntervalSeconds:磁碟失效執行緒執行時間間隔,預設是120秒。
     memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理記憶體。預設策略是LRU(最近最少使用)。你可以設定為FIFO(先進先出)或是LFU(較少使用)。
     clearOnFlush:記憶體數量最大時是否清除。
     memoryStoreEvictionPolicy:可選策略有:LRU(最近最少使用,預設策略)、FIFO(先進先出)、LFU(最少訪問次數)。
     FIFO,first in first out,這個是大家最熟的,先進先出。
     LFU, Less Frequently Used,就是上面例子中使用的策略,直白一點就是講一直以來最少被使用的。如上面所講,快取的元素有一個hit屬性,hit值最小的將會被清出快取。
     LRU,Least Recently Used,最近最少使用的,快取的元素有一個時間戳,當快取容量滿了,而又需要騰出地方來快取新的元素的時候,那麼現有快取元素中時間戳離當前時間最遠的元素將被清出快取。
  -->

</ehcache>