Mybatis筆記

感謝狂神,講解清晰,以下是原視頻【狂神說Java】Mybatis最新完整教程IDEA版通俗易懂_嗶哩嗶哩_bilibili的筆記

環境:

  • JDK 1.8
  • Mysql 8.0+
  • maven 3.6.1
  • IDEA

回顧:

  • JDBC
  • Mysql
  • Java基礎
  • Maven
  • Junit

SSM框架:配置文件的。(看官網文檔);

簡介

什么是Mybatis

  • MyBatis 是一款優秀的持久層框架
  • 它支持自定義 SQL、存儲過程以及高級映射。
  • MyBatis 免除了幾乎所有的 JDBC 代碼以及設置參數和獲取結果集的工作。
  • MyBatis 可以通過簡單的 XML 或注解來配置和映射原始類型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 對象)為數據庫中的記錄。
  • MyBatis本是apache的一個iBatis,2010年這個項目由apache software foundation遷移到了google code,并且改名為MyBatis。2013年11月遷移到Github。

如何獲得:

  • Maven倉庫
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
</dependency>

持久化

數據持久化

  • 持久化就是將程序的數據在瞬時狀態和持久狀態轉化的過程
  • 內存:斷電即失
  • 數據庫(jdbc),io文件持久化
為什么需要持久化?
  • 有的對象不能失去
  • 內存貴

持久層

Dao層、Sevice層、Controller層...

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

為什么需要Mybatis?

  • 把數據存到數據庫中
  • 方便
  • 傳統的JDBC代碼太復雜。簡化、框架、自動化
  • 不用Mybatis也可以。

優點:

  • 解除SQL和程序代碼的耦合
  • 提供映射標簽,支持對象與數據庫的orm字段關系映射
  • 提供對象關系映射標簽,支持對象關系組件維護
  • 提供xml標簽,支持編寫動態sql

第一個Mybatis程序

思路:搭建環境->導入Mybatis->編寫代碼->測試。

搭建環境

搭建數據庫

CREATE TABLE user (
    id INT(20) NOT NULL,
    name VARCHAR(30) DEFAULT NULL,
    pwd VARCHAR(30) DEFAULT NULL,
    PRIMARY KEY(id)
)ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO USER (id, name, pwd) VALUES
(1, '狂神', '123456'),
(2, '張三', '123456'),
(3, '李四', '123890')

新建項目

  1. 新建一個普通的maven項目
  2. 刪除src目錄
  3. 導入maven依賴(版本自定)
<!--  導入依賴  -->
    <dependencies>
        <!--Mysql-->
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>

        <!--Mybatis-->
        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>

        <!--Junit-->
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

編寫mybatis工具類:

public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    static {
        String resource = "mybatis-config.xml";
        // InputStream inputStream = null;
        try (InputStream inputStream = Resources.getResourceAsStream(resource);){
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 通過factory獲得sqlsession
    public static SqlSession getSqlSession() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        return sqlSession;
    }

}

目錄結構:

mybatis配置、用戶、用戶Dao以及用戶Mapper:mybatis-config.xml、User、UserDao、UserMapper.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="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF8&amp;serverTimezone=UTC"/>
                <property name="username" value="**你自己的用戶名**"/>
                <property name="password" value="你自己的用戶密碼"/>
            </dataSource>
        </environment>
    </environments>
    <!--Mapper.XML-->
    <mappers>
        <mapper resource="com/dao/UserMapper.xml"/>
    </mappers>
</configuration>

其中,serverTimezone是針對MySQL8的設置。

public class User {
    private int id;
    private String name;
    private String pwd;

    public User() {
    }

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

    @Override
    public String toString() {
        return "User{" + "id=" + id + ", name='" + name + '\'' + ", pwd='" + pwd + '\'' + '}';
    }
}
public interface UserDao {
    List<User> getUserList();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dao.UserDao">
    <!--select 用戶-->
    <select id="getUserList" resultType="com.pojo.User">
        select * from mybatis.user
    </select>
</mapper>

然后編寫一個測試類:

public class UserDaoTest {
    @Test
    public void test() {
        // 得到Session
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        // form-1:getMapper
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        List<User> userList = mapper.getUserList();
        for (User user : userList) {
            System.out.println(user);
        }
        sqlSession.close();
    }
}

注意1:此時,會報出找不到xml文件的錯誤,主要是由于maven導致的問題,應該在pom.xml的配置文件下添加如下語句:

<!--約定大于配置,需要配置maven導出文件-->
    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>

注意2:如果出現“ UTF-8 序列的字節 2 無效。”是因為項目的默認配置編碼不是UTF-8。解決方法是:將IDEA設置為UTF-8編碼。或者將中文注釋的字符去掉。

最終輸出為:

User{id=1, name='狂神', pwd='123456'}
User{id=2, name='張三', pwd='123456'}
User{id=3, name='李四', pwd='123890'}

總結:編寫Mybatis工具類->寫Mybatis配置->寫實體類->寫實體類Mapper
問題:1、注釋帶中文字符可能會報錯;2、Maven約定大于配置,可能存在找不到配置文件的情況,此時要對Maven進行配置;3、mysql8和mysql5配置存在差異,體現在mybatis的xml配置文件中。

繼續深入其他語句(CRUD)

  1. Namespace要對應到Dao;

  2. Select選擇查詢語句

  • id:對應namespace的方法名
  • resultType:sql返回值
  • parameterType:參數類型
<select id="getUserById" parameterType="int" resultType="com.pojo.User">
        select * from mybatis.user where id = #{id}
</select>
  1. Insert
<insert id="insertUser" parameterType="com.pojo.User">
        insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd});
</insert>
  1. update
<update id="updateUser" parameterType="com.pojo.User">
        update mybatis.user set name=#{name}, pwd=#{pwd} where id = #{id};
</update>
  1. delete
<delete id="deleteUser" parameterType="int">
        delete from mybatis.user where id = #{id};
</delete>

總結:編寫接口->編寫對應的mapper里的sql語句->測試(增刪改需要提交事務)

  1. 萬能Map
<insert id="addUser" parameterType="map">
        insert into mybatis.user (id, name, pwd) values (#{userId}, #{userName}, #{passWord});
</insert>
  1. 模糊查詢
<select id="getUserLike" resultType="com.pojo.User">
        select * from mybatis.user where name like "%" #{value} "%";
</select>

總結:

  1. map傳遞使用參數為Key名
  2. Object傳遞使用參數為屬性名
  3. 單個基本類型參數可以直接在sql取到
  4. 多個參數用map或者注解
  5. 通配符盡可能固定,需要考慮到注入問題。

配置解析

1、 核心配置文件

  • mybatis-config.xml
  • MyBatis 的配置文件包含了會深深影響 MyBatis 行為的設置和屬性信息。

重點在:properties、environments、mappers等。

  • transactionManager默認為JDBC
  • dataSource默認為POOLED
  • 學會如何配置多套environment
  • properties可以動態替換(使用外部的配置文件比如"db.properties")
  • typeAliases可以給實體類起別名(可以指定包名,找到對應的Beans)
  • Settings里面"logImpl",“useGeneratedKeys”,“mapUnderscoreToCamelCase”等需要了解。
  • Others

配置文件里面可以使用properties來引入外部文件里的屬性:(配置的位置需要在Configuration里面的最開始,不然會報錯)

<properties resource="db.properties">
    <property name="" value=""/>
</properties>

db.properties的內容(即屬性)。(原本的&用&代替,不然會報錯)

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF8&serverTimezone=UTC
username=你自己的用戶名
password=你自己的密碼

typeAliases(別名)的使用

直接指定別名:(實體類少時,推薦使用)

<typeAliases>
        <typeAlias type="com.pojo.User" alias="User"/>
</typeAliases>

指定包名讓MyBatis自己去找對應的:(實體類多時,推薦使用)

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

或者在User里面使用注解:

@Alias("User")
public class User

就可以使用User來代替之前的com.pojo.User

<select id="getUserList" resultType="User">
        select * from mybatis.user
</select>

Mappers

用class來配置mapper時,接口和配置文件要同名且在同一個包下,用package配置同理。以下是錯誤示范:

<mappers>
    <mapper class="com.dao.UserDao"/>
</mappers>

解決:應該將UserDao重命名為UserMapper。

最終,可以通過三種方式來配置,任選其一都可以測試成功:

<mappers>
        <mapper resource="com/dao/UserMapper.xml"/>
        <mapper class="com.dao.UserMapper"/>
        <package name="com.dao"/>
</mappers>

作用域和生命周期

  • 一旦創建了 SqlSessionFactory,就不再需要SqlSessionFactoryBuilder了。(只用一次)
  • SqlSessionFactory 一旦被創建就應該在應用的運行期間一直存在,沒有任何理由丟棄它或重新創建另一個實例。(單例)
  • 每個線程都應該有它自己的 SqlSession 實例。SqlSession 的實例不是線程安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域。(用完關閉,如下所示:)
try (SqlSession session = sqlSessionFactory.openSession()) {
  // 你的應用邏輯代碼
}

ResultMap(解決對象屬性名和數據表字段名不一致的問題)詳見:XML 映射器_MyBatis中文網

比如User此時為:

public class User {
    private int id;
    private String name;
    private String password;

屬性password與數據庫中pwd不同名,此時查詢出來的結果如下。

User{id=1, name='狂神', password='null'}
User{id=2, name='張三', password='null'}
User{id=3, name='李四', password='null'}

可見,丟失了最后一個字段,有兩種解決方法。

  1. 別名(修改查詢語句):
    select id, name, pwd as password from mybatis.user

  2. ResultMap(對外部 resultMap 的命名引用。結果映射是 MyBatis 最強大的特性,如果你對其理解透徹,許多復雜的映射問題都能迎刃而解。 resultType 和 resultMap 之間只能同時使用一個。):設定一個UserMap的resultMap,類型為User。然后,指定方法的resultMap為UserMap,即可完成映射。

<resultMap id="UserMap" type="User">
        <result column="id" property="id"/>
        <result column="pwd" property="password"/>
        <result column="name" property="name"/>
</resultMap>
<select id="getUserList" resultMap="UserMap">
        select * from mybatis.user
</select>

總結:

  • ResultMap 元素是 MyBatis 中最重要最強大的元素。
  • ResultMap 的設計思想是,對簡單的語句做到零配置,對于復雜一點的語句,只需要描述語句之間的關系就行了。
  • ResultMap 的優秀之處——你完全可以不用顯式地配置它們。

日志

日志工廠

異常排錯:sout、debug、日志工廠

使用logImpl,指定 MyBatis 所用日志的具體實現,未指定時將自動查找。可選為:SLF4J(掌握?) | LOG4J(出Bug,掌握) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING(掌握) | NO_LOGGING。默認無。

對mybatis-config.xml進行配置,注意settings所在的順序位置。比如下面的命令行LOG。

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

Log4j

是什么(引自百度百科):Log4j是Apache的一個開源項目,通過使用Log4j,我們可以控制日志信息輸送的目的地是控制臺、文件、GUI組件,甚至是套接口服務器、NT的事件記錄器、UNIX Syslog守護進程等。我們也可以控制每一條日志的輸出格式;通過定義每一條日志信息的級別,通過一個配置文件來靈活地進行配置,精細控制日志的生成過程,而不改代碼。

怎么用

  • 先導包:
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
  <groupId>log4j</groupId>
  <artifactId>log4j</artifactId>
  <version>1.2.17</version>
</dependency>
  • 再配置:resources/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/kuang.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
  • 再把settings改成使用LOG4J:
<settings>
        <setting name="logImpl" value="LOG4J"/>
</settings>
  • 使用:
static Logger logger = Logger.getLogger(userDaoTest.class); // 當前類
@Test
public void testLog4j(){ //多種日志的級別
  logger.info("info:進入了testlog4j");
  logger.debug("debug:進入了testlog4j");
  logger.warn("warn:進入了testlog4j");
  logger.error("error:進入了testlog4j");
}
  • 輸出:
[dao.userDaoTest]-info:進入了testlog4j
[dao.userDaoTest]-debug:進入了testlog4j
[dao.userDaoTest]-warn:進入了testlog4j
[dao.userDaoTest]-error:進入了testlog4j

分頁

目的:減少運算數據量。

1、 LIMIT實現分頁

實現sql的相關接口。

List<User> getUserByLimit(Map<String, Integer> map);
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
        select * from mybatis.user limit #{startIndex}, #{endIndex};
</select>

進行測試。

@Test
public void testGetUserByLimit(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    HashMap<String, Integer> parameterMap = new HashMap<>();
    parameterMap.put("startIndex", 1);
    parameterMap.put("endIndex", 2);
    List<User> userByLimit = mapper.getUserByLimit(parameterMap);
    for (User user: userByLimit) {
        System.out.println(user);
    }
    sqlSession.close();
}

2、RowBounds實現分頁

List<User> getUserByRowBounds();
<select id="getUserByRowBounds" resultMap="UserMap">
  select * from mybatis.user;
</select>
@Test
public void testGetUserByRowBounds() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    // Java層面來直接selectList
    RowBounds rowBounds = new RowBounds(1, 2);
    List<User> userList = sqlSession.selectList("com.dao.UserMapper.getUserByRowBounds", null, rowBounds);
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

3、分頁插件
PageHelper等。

使用注解開發

面向接口編程

目的:解耦

  • 個體的抽象:abstract class
  • 個體的某個方面的抽象:interface

三個面向

  • 面向對象:考慮對象的屬性和方法。
  • 面向過程:以流程(事務)考慮實現。
  • 接口設計與非接口設計:與另外兩個考慮的不是一個問題,更多的是對系統整體的架構。

注解開發

可以不配置對應mapper的xml,使用簡單的注解。

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

然后,綁定接口:

<!--Bind interfaces-->
<mappers>
        <mapper class="com.dao.UserMapper"/>
</mappers>
@Test
public void testGetUsers() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> users = mapper.getUsers();
    for (User user : users) {
        System.out.println(user);
    }
    sqlSession.close();
}
  • 實現:反射
  • 底層:動態代理

MyBatis執行流程

加載配置->SqlSessionFactoryBuilder(構建XMLConfigBuilder->轉換Configuration)->SqlSessionFactory->(transaction事務管理->executor->sqlSession->CRUD->是否執行成功)->提交事務->關閉

繼續CRUD

增改刪(注解版)及測試:

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

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

@Delete("delete from user where id = #{id}")
int deleteUser(int id);
@Test
public void testAddUser() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int res = mapper.addUser(new User(6, "aaa", "1234555"));
    if (res > 0){
        System.out.println("插入成功");
    }
    sqlSession.commit();
    sqlSession.close();
}

@Test
public void testUpdateUser() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int res = mapper.updateUser(new User(6, "aaa", "6666"));
    if (res > 0){
        System.out.println("改變成功");
    }
    sqlSession.commit();
    sqlSession.close();
}

@Test
public void testDeleteUser() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int res = mapper.deleteUser(6);
    if (res > 0){
        System.out.println("刪除成功");
    }
    sqlSession.commit();
    sqlSession.close();
}

關于@Param注解

  • String或者基本類的需要加上
  • 引用類型不需要加
  • 只有一個基本類型可以忽略
  • SQL中引用的就是@Param中的屬性名

#{}和${}

  • $直接拼接,無法防止注入
  • $多用在傳入數據庫參數時
  • 盡量使用#

Lombok

是什么:Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.

  • 通過IDEA進行插件安裝
  • 導包
<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.16</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

Lombok注解內容:

@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass
Lombok config system
Code inspections
Refactoring actions (lombok and delombok)

  • @Data:無參構造,get,set,toString,hashcode,equals
  • @AllArgsConstructor:有參構造
  • @NoArgsConstructor:無參構造
  • @Getter
  • @Setter
  • @ToString

復雜查詢環境

  • 多個學生對應/關聯一個老師(多對一)
  • 集合,一個老師有很多學生(一對多)

先建表搭建環境:

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');

流程:導入lombok->建立實體類->建立Mapper接口->建立Mapper.xml文件->在核心配置文件中綁定Mapper->測試查詢是否成功。

多對一

按照查詢嵌套處理

查找學生的時候,通過外鍵查詢對應的老師。

    <select id="getAllStudents" resultMap="StudentTeacher">
        select * from student;
    </select>

    <resultMap id="StudentTeacher" type="Student">
        <result property="id" column="id"/>
        <result property="name" column="name"/>

        <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
    </resultMap>

    <select id="getTeacher" resultType="Teacher">
        select * from teacher where id = #{tid};
    </select>

按照結果嵌套處理

    <select id="getAllStudents2" 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 column="sid" property="id"/>
        <result column="sname" property="name"/>
        <association property="teacher" javaType="Teacher">
            <result column="tid" property="id"/>
            <result column="tname" property="name"/>
        </association>
    </resultMap>

一對多

按照結果嵌套處理

    <select id="getTeacherById" resultMap="TeacherStudent" parameterType="int">
        select t.id tid, t.name tname, s.id sid, s.name sname
        from teacher t, student s
        where t.id = #{tid} and t.id = s.tid;
    </select>

    <resultMap id="TeacherStudent" type="Teacher">
        <result column="tname" property="name"/>
        <result column="tid" property="id"/>

        <collection property="students" ofType="Student">
            <result column="sid" property="id"/>
            <result column="sname" property="name"/>
            <result column="tid" property="tid"/>
        </collection>
    </resultMap>

按照查詢嵌套處理

    <select id="getTeacherById2" resultMap="TeacherStudent2">
        select * from teacher where id = #{tid};
    </select>

    <resultMap id="TeacherStudent2" type="Teacher">
        <result property="id" column="id"/>
        <result property="name" column="name"/>

        <collection property="students" javaType="ArrayList" ofType="Student" select="getStudent" column="id"/>
    </resultMap>

    <select id="getStudent" resultType="Student">
        select * from student where tid = #{tid};
    </select>

總結:

  • 保證SQL可讀性
  • 一對多和多對一的字段和屬性名的對應問題
  • 問題如果不好排查,使用日志系統,如:Log4j
  • 最終SQL優化的方向為:學習和理解Mysql引擎、InnoDB底層原理、索引、索引優化!

動態SQL

是什么:根據不同條件生成SQL語句。

搭建環境:

CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客標題',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '創建時間',
`views` INT(30) NOT NULL COMMENT '瀏覽量'
)ENGINE=INNODB DEFAULT CHARSET=utf8;
    @Test
    public void testAddBlogs() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

        Blog blog = new Blog();
        blog.setId(IDutils.getID());
        blog.setAuthor("KyoDante");
        blog.setCreateTime(Timestamp.from(Instant.now()));
        blog.setTitle("Mybatis如此簡單");
        blog.setViews(7777);
        mapper.addBlog(blog);

        blog.setId(IDutils.getID());
        blog.setTitle("Spring如此簡單");
        blog.setCreateTime(Timestamp.from(Instant.now()));
        mapper.addBlog(blog);

        blog.setId(IDutils.getID());
        blog.setTitle("Java如此簡單");
        blog.setCreateTime(Timestamp.from(Instant.now()));
        mapper.addBlog(blog);

        blog.setId(IDutils.getID());
        blog.setTitle("微服務如此簡單");
        blog.setCreateTime(Timestamp.from(Instant.now()));
        mapper.addBlog(blog);

        sqlSession.commit();

        sqlSession.close();
    }
public class IDutils {
    public static String getID() {
        return UUID.randomUUID().toString().replace("-", "");
    }
}

IF

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

常用標簽

where和if搭配(只要滿足就填入SQL)

    <select id="queryBlogChoose" 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>

Choose(類似Switch,多選一)

    <select id="queryBlogChooseWhen" 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>

Trim

與 set 元素等價的自定義 trim 元素:

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

sql和include

  • 用sql抽取復用的部分
    <sql id="if-title-author">
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </sql>
  • 用include將該片段包括進去
    <select id="queryBlogChoose" parameterType="map" resultType="Blog">
        select * from blog
        <where>
            <include refid="if-title-author"/>
        </where>
    </select>

FOREACH

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

緩存

是什么:查詢連接數據庫,耗資源,存到某些地方,下次可以直接使用!內存->緩存(解決高并發性能問題)
為什么:減少和數據庫的交互開銷,提高系統效率
哪里使用:經常查詢且不經常改變的數據。

  • Mybatis有一級緩存二級緩存
  • 默認為開啟一級緩存。(SqlSession級別的緩存;也稱為本地緩存)
  • 二級緩存是手動開啟和配置,基于namespace級別的緩存
  • 為了提高擴展性,定義了緩存接口Cache,可以實現它來定義二級緩存

一級緩存

    @Test
    public void test() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.queryUserById(1);

        System.out.println("==================");
        User user2 = mapper.queryUserById(1);

        System.out.println(user2);
        System.out.println(user);
        System.out.println(user == user2);
        
        sqlSession.close();
    }
  • 增刪改會刷新緩存
    @Test
    public void test() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.queryUserById(1);

        User user3 = new User();
        user3.setId(2);
        user3.setName("aaaa");
        user3.setPwd("bbbb");
        int res = mapper.updateUserById(user3);
        if (res > 0) {
            System.out.println("改變成功");
        }
        System.out.println("==================");
        User user2 = mapper.queryUserById(1);

        System.out.println(user2);
        System.out.println(user);
        System.out.println(user == user2);

        sqlSession.close();
    }
  • 或者清除緩存
    sqlSession.clearCache();

二級緩存

為了什么:一級緩存只在SqlSession作用域內,會話沒了,還會存在于二級緩存中。新的會話可以從二級緩存中獲得內容。不同mapper放在對應的map中。

在Mapper中設置(如果直接這么設置,需要序列化實體類,即implements Serializable)

<cache/>

在核心配置中使用(默認開啟):

<setting name="cacheEnabled" value="true"/>
  • 最開始是一級緩存
  • 只有在關閉會話,或者提交會話的時候,才會轉到二級緩存

緩存原理

提高查詢效率
從用戶的角度:先查二級再查一級。
從程序的角度:先從SqlSession,也即一級緩存;再到Mapper,也即二級緩存。

EhCache

  • 導包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-ehcache</artifactId>
            <version>1.2.1</version>
        </dependency>
  • 配置resources/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 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"/>
</ehcache>

使用

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

Redis:K-V來實現緩存。(現在常用)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容