Java框架-01-MyBatis

1. MyBatis簡介

  1. MyBatis提供了數(shù)據(jù)庫查詢的自動對象綁定功能;SQL寫在XML中,便于統(tǒng)一管理和優(yōu)化,解除了SQL與代碼的耦合;執(zhí)行效率比Hibernate快;對于復雜SQL,使用MyBatis會更加靈活。但關聯(lián)的表或字段較多時,SQL工作量很大;而且SQL依賴于數(shù)據(jù)庫,導致數(shù)據(jù)庫移植性較差。
  2. Hibernate是全自動,Hibernate完全可以通過對象關系模型實現(xiàn)對數(shù)據(jù)庫的操作,擁有完整的JavaBean對象與數(shù)據(jù)庫的映射結構來自動生成SQL;對于單表的CRUD,使用Hibernate開發(fā)更加高效。但學習和精通的門檻較高,而且由于是自動生成SQL,因此無法直接維護SQL。
  3. MyBatis官網(wǎng):https://mybatis.org/mybatis-3/
  4. MyBatis作用:
    1. 減少JDBC的重復代碼
    2. 使數(shù)據(jù)庫環(huán)境信息的硬編碼和執(zhí)行的SQL語句的硬編碼解耦
  5. MyBatis分頁插件PageHelper網(wǎng)址:https://pagehelper.github.io/

2. MyBatis基本使用

以查詢user表中所有user為例

2.1 創(chuàng)建Maven工程,添加坐標

<dependencies>
    <!--添加坐標-->
    <!--junit單元測試-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--lombok的依賴-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.10</version>
        <scope>provided</scope>
    </dependency>
    <!--mysql驅(qū)動-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <!--mybatis的依賴-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.3</version>
    </dependency>
</dependencies>

2.2 創(chuàng)建POJO

-- 創(chuàng)建user表
CREATE TABLE user(
    id INT PRIMARY KEY AUTO_INCREMENT COMMENT "id",
    username VARCHAR(255) NOT NULL DEFAULT "" COMMENT "用戶名",
    password VARCHAR(16) NOT NULL DEFAULT "123456" COMMENT "密碼",
    balance DOUBLE(16, 3) NOT NULL DEFAULT 0.0 COMMENT "余額"
)ENGINE=INNODB, CHARSET=utf8, COMMENT="用戶表";

-- 添加用戶
INSERT INTO user VALUES
    (NULL, 'user1', 'password1', 1000),
    (NULL, 'user2', 'password2', 2000),
    (NULL, 'user3', 'password3', 3000);


@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    private Integer id;
    private String username;
    private String password;
    private Double balance;
}

2.3 創(chuàng)建PojoDao接口

原則:MyBatis的所有PojoDao接口,方法參數(shù)只設置一個;

由于是全類名+方法名的保存和尋找策略,因此PojoDao接口里的方法不能重載

PojoDao接口的工作原理是JDK動態(tài)代理

public interface UserDao {
    void addUser(User user);

    void deleteById(Integer id);

    void updateUser(User user);

    List<User> findAll();
}

2.4 創(chuàng)建外部jdbc配置文件

在resources下創(chuàng)建jdbc.properties文件:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db01?useSSL=false
jdbc.username=root
jdbc.password=123456

2.5 創(chuàng)建MyBatis核心配置文件*

  1. 存儲在resources根路徑下
  2. 文件名隨意(如:SqlMapConfig.xml)
  3. 需要引入約束

核心配置文件的順序:

  1. properties:引入外部properties文件

  2. settings:全局配置參數(shù)

  3. typeAliases:給類型設置別名

    核心配置文件:typeAliases定義單個別名
    <typeAliases>
        <typeAlias type="com.liu2m.pojo.User" alias="User"></typeAlias>
    </typeAliases>
    核心配置文件:typeAliases批量定義別名
    <typeAliases>
        <package name="com.liu2m.pojo"/>
    </typeAliases>
    
    修改PojoDao.xml:
    <select id="findAll" resultType="User">
        select * from user
    </select>
    
  4. typeHandlers:類型處理器

  5. objectFactory:對象工廠

  6. objectWrapperFactory

  7. reflectorFactory

  8. plugins:插件

  9. environments:環(huán)境配置

  10. databaseIdProvider:數(shù)據(jù)庫廠商標識

  11. mappers:映射器

<?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>
    <!--引入外部properties文件-->
    <properties resource="jdbc.properties"></properties>
    <!--給類型設置別名-->
    <typeAliases>
        <package name="com.liu2m.pojo"/>
    </typeAliases>
    <!--配置連接數(shù)據(jù)庫的環(huán)境
    default:指定使用哪一個環(huán)境-->
    <environments default="dev">
        <!--一個連接數(shù)據(jù)庫的環(huán)境
        id:環(huán)境變量名-->
        <environment id="dev">
            <!--配置事務管理者
            MyBatis事務使用的是jdbc-->
            <transactionManager type="JDBC"></transactionManager>
            <!--配置連接池
            UNPOOLED:不使用連接池
            POOLED:使用mybatis內(nèi)置的連接池-->
            <dataSource type="POOLED">
                <!--value值可以直接寫,也可以通過${}設置
                ${}中寫properties引入的文件中的屬性名-->
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>

        <environment id="test">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/db01?useSSL=false&amp;characterEncoding=UTF8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <!--加載所有的映射文件-->
    <mappers>
        <!--引入一個映射文件-->
        <!--方式1:通過resource屬性引入映射文件的路徑-->
        <!--<mapper resource="com/liu2m/dao/UserDao.xml"/>-->

        <!--方式2:掃描接口-->
        <!--配置單個接口-->
        <!--<mapper class="com.liu2m.dao.UserDao"></mapper>-->
        <!--批量配置-->
        <package name="com.liu2m.dao"/>
    </mappers>
</configuration>

2.6 創(chuàng)建SQL映射文件*

  1. 存儲在resources里面
  2. 全路徑和文件名要和相應的PojoDao接口的相同(在resources里創(chuàng)建多級文件夾時用/分隔,而不是.)
  3. 需要引入約束

根標簽mapper的namespace屬性的值為對應的PojoDao接口的全限定名

根標簽mapper下的每一個子標簽對應PojoDao接口中的一個方法:

  1. 查詢方法對應select標簽
  2. 添加方法對應insert標簽
  3. 刪除方法對應delete標簽
  4. 修改方法對應update標簽

子標簽的id對應方法的名字

子標簽的parameterType對應方法的形參類型

子標簽的resultType(只有select標簽有)對應方法的返回值類型:

  1. 如果返回值類型是簡單類型,則直接寫對應的Java類型
  2. 如果返回值類型是POJO、Map,則直接寫POJO的全限定名
  3. 如果返回值類型是Collection,則resultType是集合中元素的類型
  • 結果集的字段名和POJO的屬性名能夠?qū)獣r,resultType才可以自動映射

使用參數(shù)中的數(shù)據(jù):

  1. 如果參數(shù)為pojo、map,則通過#{屬性名或Map的key}來引入對應的值
  2. 如果參數(shù)為基本類型數(shù)據(jù),則通過#{任意字符串}來引入對應的值
  3. 如果參數(shù)為pojo包含pojo,如ResultBean中包含User,則使用#{user.username}

resultMap標簽:解決查詢出來的結果的列名和JavaBean屬性不一致的情況

  1. id屬性:映射規(guī)則的唯一id
  2. type屬性:將SQL查詢出來的結果手動映射到哪個類
<?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.liu2m.dao.UserDao">
    <resultMap id="findAllMap" type="User">
        <!--id:查詢結果集的唯一標識;如果唯一約束是多個字段段,則定義多個id-->
        <id property="id" column="u_id"></id>
        <!--
        property:指定的POJO類的屬性
        column:sql查詢出來的字段名-->
        <result property="username" column="u_username"></result>
        <result property="password" column="u_password"></result>
        <result property="balance" column="u_balance"></result>
    </resultMap>
    <select id="findAll" resultMap="findAllMap">
        select id as u_id, username as u_username, password as u_password, balance as u_balance from user
    </select>

    <!--selectKey可以獲取主鍵,存儲在對應的POJO對象中
    keyColumn:要查詢的字段名
    keyProperty:將查詢到的結果賦值到哪個屬性
    resultType:查詢到的結果的類型
    order:在執(zhí)行添加之前還是之后執(zhí)行selectKey里的SQL-->
    <insert id="addUser" parameterType="com.liu2m.pojo.User">
        <selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
            select LAST_INSERT_ID()
        </selectKey>
        insert into user values (null, #{username}, #{password}, #{balance})
    </insert>

    <update id="updateUser" parameterType="com.liu2m.pojo.User">
        update user set username = #{username}, password = #{password}, balance = #{balance}
    </update>

    <delete id="deleteById" parameterType="java.lang.Integer">
        delete from user where id = #{id}
    </delete>
</mapper>

2.7 單元測試

public class UserDaoTest {
    InputStream is;
    SqlSession sqlSession;
    UserDao userDao;

    @Before
    public void init() throws IOException {
        // 1. 創(chuàng)建SqlSessionFactoryBuilder對象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 2. 讀取核心配置文件,轉換成字節(jié)輸入流
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        // 3. 創(chuàng)建SqlSessionFactory對象
        SqlSessionFactory sessionFactory = sqlSessionFactoryBuilder.build(is);
        // 4. 創(chuàng)建SqlSession對象
        sqlSession = sessionFactory.openSession();
        // 5. 創(chuàng)建UserDao的代理對象
        userDao = sqlSession.getMapper(UserDao.class);
    }

    @Test
    public void testFindAll() {
        // 6. 調(diào)用需要執(zhí)行的方法
        List<User> userList = userDao.findAll();
        for (User user : userList) {
            System.out.println(user);
        }
    }

    @Test
    public void testAddUser() {
        User user = new User(0, "user4", "654321", 4000.0);
        userDao.addUser(user);
        System.out.println(user.getId());
    }

    @After
    public void destroy() throws IOException {
        // 在CUD操作中,需要通過sqlSession.commit()方法來執(zhí)行提交操作
        sqlSession.commit();
        // 7. 釋放資源
        sqlSession.close();
        is.close();
    }
}

2.8 #{}與${}的區(qū)別*

  1. 傳參的區(qū)別:
    1. #{}先用?占位,然后再設置參數(shù),可以防止SQL注入
    2. ${}直接拼接SQL語句
  2. #{}不能寫在引號里,而${}必須要寫在引號里
  3. pojo、map類型的參數(shù),#{}和${}括號里都是寫屬性名或Map的key
  4. 基本類型、字符串類型的參數(shù),#{}和${}括號里可以寫任意字符串
  • 模糊查詢:
    1. "%"#{username}"%"
    2. concat("%", #{username}, "%")
    3. "%${value}%"

2.9 自定義SqlSessionFactory工具類

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 SqlSessionFactoryUtils {
    private static InputStream is;
    private static SqlSessionFactory sessionFactory;

    static {
        try {
            // 1. 創(chuàng)建SqlSessionFactoryBuilder對象
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            // 2. 讀取核心配置文件,轉換成字節(jié)輸入流
            is = Resources.getResourceAsStream("SqlMapConfig.xml");
            // 3. 創(chuàng)建SqlSessionFactory對象
            sessionFactory = sqlSessionFactoryBuilder.build(is);
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 獲取一個SqlSession
     */
    public static SqlSession openSession() {
        SqlSession sqlSession = null;
        // 4. 創(chuàng)建SqlSession對象
        sqlSession = sessionFactory.openSession();
        return sqlSession;
    }

    /**
     * 提交事務并關閉資源
     */
    public static void commitAndClose(SqlSession sqlSession) {
        sqlSession.commit();
        sqlSession.close();
    }

    /**
     * 回滾事務并關閉資源
     */
    public static void rollbackAndClose(SqlSession sqlSession) {
        sqlSession.rollback();
        sqlSession.close();
    }
}

3. MyBatis日志

  1. 導入坐標:

    <!--使用log4j日志打印框架打印mybatis日志-->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.6.6</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.6.6</version>
    </dependency>
    
  2. 在resources下創(chuàng)建log4j.properties文件:

    #日志級別:ERROR > WARN > INFO > DEBUG
    log4j.rootLogger=DEBUG,stdout
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=[%-5p] %t %l %d %rms:%m%n
    log4j.appender.file=org.apache.log4j.FileAppender
    log4j.appender.file.File=E:\\Logs\\IdeaLogs\\IdeaLogs.log
    log4j.appender.file.layout=org.apache.log4j.PatternLayout
    log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS\} %-5p [%t] {%c}-%m%n
    

4. Mybatis動態(tài)SQL*

動態(tài)SQL:SQL語句的主體結構,在編譯時無法確定,只有等到程序運行后,在執(zhí)行過程中才能確定

4.1 if標簽

滿足test屬性的邏輯表達式,則if標簽里面的語句有效

多條件查詢時,每次查詢的條件不確定有哪些,就可以使用if標簽

當參數(shù)值為int的0時,MyBatis會將參數(shù)值其轉化為空字符串

select * from user
<!--如果參數(shù)balance大于0,則添加balance的條件-->
<if test="balance != null and balance > 0">
    where balance = #{balance}
</if>

4.2 where標簽

select * from user
<!--where標簽的作用:
如果有if條件成立,則會在SQL語句中添加WHERE關鍵字,并且還會自動去掉第一個條件的and-->
<where>
    <if test="balance != null and balance > 0">
        and balance = #{balance}
    </if>
    <if test="password != null and password.length > 6">
        and password = #{password}
    </if>
</where>

4.3 foreach標簽

<!--刪除所有在id集合中的行-->
delete from user
<!--
collection:要遍歷的對象;如果要遍歷的對象是一個集合,就寫list
item:遍歷出來的每一個元素
index:遍歷出來的每一個元素的下標
separator:分隔符
open:在遍歷出來的第一個元素之前拼接的字符串
close:在遍歷出來的最后一個元素之后拼接的字符串
-->
<foreach open="where id in (" close=")" collection="list" item="id" index="i" separator=", ">
    #{id}
</foreach>

4.4 sql標簽和include標簽

sql標簽可以定義公共的sql片段,需要時可以用include標簽引入sql片段

<sql id="findAll">
    select * from user
</sql>
<select id="findAll" resultType="User" parameterType="int">
    <include refid="findAll"></include>
    where id = #{id}
</select>

5. Mybatis多表關聯(lián)查詢*

建立數(shù)據(jù)庫環(huán)境:

DROP TABLE IF EXISTS user;
-- 創(chuàng)建用戶表
CREATE TABLE user(
    uid INT PRIMARY KEY AUTO_INCREMENT COMMENT "id",
    username VARCHAR(255) NOT NULL DEFAULT "" COMMENT "用戶名",
    password VARCHAR(16) NOT NULL DEFAULT "123456" COMMENT "密碼",
    balance DOUBLE(16, 3) NOT NULL DEFAULT 0.0 COMMENT "余額"
)ENGINE=INNODB, CHARSET=utf8, COMMENT="用戶表";
-- 添加用戶
INSERT INTO user VALUES
    (NULL, 'user1', 'password1', 1000),
    (NULL, 'user2', 'password2', 2000),
    (NULL, 'user3', 'password3', 3000);

-- 創(chuàng)建賬戶表
CREATE TABLE account (
    aid INT AUTO_INCREMENT PRIMARY KEY,
    money DOUBLE,
    uid INT,
    CONSTRAINT fk_account_uid FOREIGN KEY (uid) REFERENCES user(uid)
)ENGINE=INNODB, CHARSET=utf8;
-- 添加賬戶
INSERT INTO account VALUES
    (NULL, 1000, 1),
    (NULL, 2000, 1),
    (NULL, 1000, 2),
    (NULL, 2000, 2),
    (NULL, 3000, 3);

-- 創(chuàng)建課程表
CREATE TABLE course(
    cid INT PRIMARY KEY AUTO_INCREMENT,
    coursename VARCHAR(255),
    book VARCHAR(255)
)ENGINE=INNODB, CHARSET=utf8;
-- 添加課程
INSERT INTO course VALUES
    (NULL, '面向?qū)ο蟪绦蛟O計', 'Java編程思想'),
    (NULL, '數(shù)據(jù)結構與算法', '算法導論'),
    (NULL, '設計模式', '大話設計模式');

-- 創(chuàng)建選課表
CREATE TABLE course_selection(
    uid INT,
    cid INT,
    CONSTRAINT fk_course_selection_uid FOREIGN KEY (uid) REFERENCES user(uid),
    CONSTRAINT fk_course_selection_cid FOREIGN KEY (cid) REFERENCES course(cid),
    PRIMARY KEY (uid, cid)
)ENGINE=INNODB, CHARSET=utf8;
-- 添加選課信息
INSERT INTO course_selection VALUES
    (1, 1),
    (1, 2),
    (1, 3),
    (2, 1),
    (2, 2),
    (3, 1);

5.1 一對一

根據(jù)賬戶id查詢賬戶信息,并關聯(lián)查詢對應的用戶表信息;由于一個賬戶只能對應一個用戶,因此為一對一查詢

在外鍵所在的POJO中新增一個外鍵參考的POJO類型的屬性

  1. 創(chuàng)建Account表的POJO:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Account implements Serializable {
        private Integer aid;
        private Double money;
        private Integer uid;
        /**
         * 表示一個Account對應一個User
         */
        private User user;
    }
    
  2. 創(chuàng)建PojoDao接口:

    public interface AccountDao {
        Account findAccountUserByAid(int aid);
    }
    
  3. 創(chuàng)建SQL映射文件:

    <?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.liu2m.dao.AccountDao">
        <!--使用resultMap自定義映射規(guī)則-->
        <resultMap id="accountUserMap" type="Account">
            <id column="aid" property="aid"></id>
            <result column="money" property="money"></result>
            <result column="uid" property="uid"></result>
            <!--使用association標簽配置一對一的映射
            property:要映射的POJO屬性的變量名
            javaType:要映射的POJO屬性的類型-->
            <association property="user" javaType="User">
                <!--result語法同上面的result-->
                <result column="uid" property="uid"></result>
                <result column="username" property="username"></result>
                <result column="password" property="password"></result>
                <result column="balance" property="balance"></result>
            </association>
        </resultMap>
        <select id="findAccountUserByAid" parameterType="int" resultMap="accountUserMap">
            SELECT * FROM user JOIN account ON user.uid = account.uid WHERE account.aid = #{aid}
        </select>
    </mapper>
    
  4. 單元測試:

    @Test
    public void test01() {
        SqlSession sqlSession = SqlSessionFactoryUtils.openSession();
        AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
        Account account = accountDao.findAccountUserByAid(1);
        System.out.println(account);
        /**
         * 結果:
         * Account(aid=1, money=1000.0, uid=1, user=User(id=null, uid=1, username=user1, password=password1, balance=1000.0))
         */
    }
    

5.2 一對多

根據(jù)用戶id查詢用戶信息,并關聯(lián)查詢對應的所有賬戶表信息;由于一個用戶可能對應多個賬戶,因此為一對多查詢

在外鍵參考的POJO中新增一個List<外鍵所在POJO>類型的屬性

  1. 創(chuàng)建User表的POJO:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User implements Serializable {
        private Integer uid;
        private String username;
        private String password;
        private Double balance;
        /**
         * 表示一個User對應多個Account
         */
        private List<Account> accountList;
    }
    
  2. 創(chuàng)建PojoDao接口:

    public interface UserDao {
        User findUserAccountByUid(int uid);
    }
    
  3. 創(chuàng)建SQL映射文件:

    <?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.liu2m.dao.UserDao">
        <!--使用resultMap自定義映射規(guī)則-->
        <resultMap id="userAccountMap" type="User">
            <id column="uid" property="uid"></id>
            <result column="username" property="username"></result>
            <result column="password" property="password"></result>
            <result column="balance" property="balance"></result>
            <!--使用collection標簽配置一對多的映射
            property:要映射的POJO屬性的變量名
            ofType:查詢出的每一行數(shù)據(jù)映射成哪一個POJO類型-->
            <collection property="accountList" ofType="Account">
                <!--result語法同上面的result-->
                <result column="aid" property="aid"></result>
                <result column="money" property="money"></result>
                <result column="uid" property="uid"></result>
            </collection>
        </resultMap>
        <select id="findUserAccountByUid" parameterType="int" resultMap="userAccountMap">
            SELECT * FROM user JOIN account ON user.uid = account.uid WHERE user.uid = #{uid}
        </select>
    </mapper>
    
  4. 單元測試:

    @Test
    public void test01() {
        SqlSession sqlSession = SqlSessionFactoryUtils.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user = userDao.findUserAccountByUid(1);
        System.out.println(user);
        /**
         * 結果:
         * User(uid=1, username=user1, password=password1, balance=1000.0, 
         * accountList=[Account(aid=1, money=1000.0, uid=1, user=null), Account(aid=2, money=2000.0, uid=1, user=null)])
         */
    }
    

5.3 多對多

根據(jù)用戶id查詢用戶所選的所有課程信息,相當于一個用戶對應多個課程

多對多關系可以看成是雙向的一對多關系

  1. 創(chuàng)建Course表的POJO:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Course implements Serializable {
        private Integer cid;
        private String coursename;
        private String book;
    }
    
  2. 創(chuàng)建User表的POJO:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User implements Serializable {
        private Integer uid;
        private String username;
        private String password;
        private Double balance;
        /**
         * 表示一個User對應多個Course
         */
        private List<Course> courseList;
    }
    
  3. 創(chuàng)建PojoDao接口:

    public interface UserDao {
        User findUserCourseByUid(int uid);
    }
    
  4. 創(chuàng)建SQL映射文件:

    <?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.liu2m.dao.UserDao">
        <!--使用resultMap自定義映射規(guī)則-->
        <resultMap id="userCourseMap" type="User">
            <id column="uid" property="uid"></id>
            <result column="username" property="username"></result>
            <result column="password" property="password"></result>
            <result column="balance" property="balance"></result>
            <collection property="courseList" ofType="Course">
                <result column="cid" property="cid"></result>
                <result column="coursename" property="coursename"></result>
                <result column="book" property="book"></result>
            </collection>
        </resultMap>
        <select id="findUserCourseByUid" parameterType="int" resultMap="userCourseMap">
            SELECT * FROM user JOIN course_selection ON user.uid = course_selection.uid JOIN course ON course.cid = course_selection.cid WHERE user.uid = #{uid};
        </select>
    </mapper>
    
  5. 單元測試:

    @Test
    public void test01() {
        SqlSession sqlSession = SqlSessionFactoryUtils.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user = userDao.findUserCourseByUid(2);
        System.out.println(user);
        /**
         * 結果:
         * User(uid=2, username=user2, password=password2, balance=2000.0,
         * courseList=[Course(cid=1, coursename=面向?qū)ο蟪绦蛟O計, book=Java編程思想), Course(cid=2, coursename=數(shù)據(jù)結構與算法, book=算法導論)])
         */
    }
    

6. Mybatis分步查詢延遲加載*

  1. 立即加載:一調(diào)用方法就立即發(fā)起查詢
  2. 延遲加載(懶加載):需要使用數(shù)據(jù)時,才發(fā)起查詢;如果暫時不需要使用數(shù)據(jù),則暫時不查詢
    1. 缺點:因為只有當需要用到數(shù)據(jù)時,才會進行數(shù)據(jù)庫查詢,這樣在大批量數(shù)據(jù)查詢時,因為查詢工作也要消耗時間,所以可能造成用戶等待時間變長,造成用戶體驗下降
    2. 優(yōu)點:先從單表查詢,需要時再從關聯(lián)表去關聯(lián)查詢;由于查詢單表要比關聯(lián)查詢多張表速度要快,因此可以提高數(shù)據(jù)庫性能
  3. 延遲加載的SQL語句類似于嵌套子查詢,分步驟查詢
  • 全局延遲加載:在MyBatis核心配置文件中配置

    <settings>
        <!--lazyLoadingEnabled=true:所有關聯(lián)對象都會延遲加載
            特定關聯(lián)關系中可設置fetchType屬性來覆蓋lazyLoadingEnabled-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--aggressiveLazyLoading=false:每個屬性會按需加載
            true:對具有懶加載特性對象的任意調(diào)用都會加載該對象的所有屬性-->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
    

6.1 association標簽實現(xiàn)分步查詢延遲加載

根據(jù)賬戶id查詢賬戶信息,在需要用戶信息時再關聯(lián)查詢對應的用戶表信息

  1. 創(chuàng)建User表和Account表的POJO:

    @NoArgsConstructor
    @AllArgsConstructor
    public class User implements Serializable {
        private Integer uid;
        private String username;
        private String password;
        private Double balance;
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Account implements Serializable {
        private Integer aid;
        private Double money;
        private Integer uid;
        /**
         * 表示一個Account對應一個User
         */
        private User user;
    }
    
  2. 創(chuàng)建PojoDao接口:

    public interface AccountDao {
        Account findAccountByAid(int aid);
    }
    
    public interface UserDao {
        User findUserByUid(int uid);
    }
    
  3. 創(chuàng)建SQL映射文件AccountDao.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.liu2m.dao.AccountDao">
        <resultMap id="accountUserMap" type="Account">
            <id column="aid" property="aid"></id>
            <result column="money" property="money"></result>
            <result column="uid" property="uid"></result>
            <!--使用association標簽配置一對一映射的延遲加載
            fetchType="lazy":局部延遲加載(配置了這個resultMap的查詢會延遲加載)
            select:調(diào)用其他的select標簽(方法)
            column:往調(diào)用的select標簽(方法)中傳入的參數(shù)-->
            <association property="user" javaType="User" fetchType="lazy"
                         select="com.liu2m.dao.UserDao.findUserByUid" column="uid">
            </association>
        </resultMap>
        <select id="findAccountByAid" parameterType="int" resultMap="accountUserMap">
            SELECT * FROM account WHERE aid = #{aid}
        </select>
    </mapper>
    
  4. 創(chuàng)建SQL映射文件UserDao.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.liu2m.dao.UserDao">
        <select id="findUserByUid" parameterType="int" resultType="User">
            SELECT * FROM user WHERE uid = #{uid}
        </select>
    </mapper>
    
  5. 單元測試:

    @Test
    public void test01() {
        SqlSession sqlSession = SqlSessionFactoryUtils.openSession();
        AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
        Account account = accountDao.findAccountByAid(1);
        System.out.println(account.getMoney());
        System.out.println(account.getUser());
    }
    

6.2 collection標簽實現(xiàn)分步查詢延遲加載

根據(jù)用戶id查詢用戶信息,在需要賬戶信息時再關聯(lián)查詢對應的所有賬戶表信息

  1. 創(chuàng)建User表和Account表的POJO:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User implements Serializable {
        private Integer uid;
        private String username;
        private String password;
        private Double balance;
        /**
         * 表示一個User對應多個Account
         */
        private List<Account> accountList;
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Account implements Serializable {
        private Integer aid;
        private Double money;
        private Integer uid;
    }
    
  2. 創(chuàng)建PojoDao接口:

    public interface UserDao {
        User findUserByUid(int uid);
    }
    
    public interface AccountDao {
        List<Account> findAccountListByUid(int uid);
    }
    
  3. 創(chuàng)建SQL映射文件UserDao.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.liu2m.dao.UserDao">
        <resultMap id="userAccountMap" type="User">
            <id column="uid" property="uid"></id>
            <result column="username" property="username"></result>
            <result column="password" property="password"></result>
            <result column="balance" property="balance"></result>
            <!--使用collection標簽配置一對多映射的延遲加載
            fetchType="lazy":局部延遲加載(配置了這個resultMap的查詢會延遲加載)
            select:調(diào)用其他的select標簽(方法)
            column:往調(diào)用的select標簽(方法)中傳入的參數(shù)-->
            <collection property="accountList" ofType="Account" fetchType="lazy"
                        select="com.liu2m.dao.AccountDao.findAccountListByUid" column="uid">
            </collection>
        </resultMap>
        <select id="findUserByUid" parameterType="int" resultMap="userAccountMap">
            SELECT * FROM user WHERE uid = #{uid}
        </select>
    </mapper>
    
  4. 創(chuàng)建SQL映射文件AccountDao.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.liu2m.dao.AccountDao">
        <select id="findAccountListByUid" resultType="com.liu2m.pojo.Account">
            SELECT * FROM account WHERE uid = #{uid}
        </select>
    </mapper>
    
  5. 單元測試:

    @Test
    public void test01() {
        SqlSession sqlSession = SqlSessionFactoryUtils.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user = userDao.findUserByUid(1);
        System.out.println(user.getUsername());
        System.out.println(user.getAccountList());
    }
    

7. MyBatis緩存-

MyBatis緩存類別:

  1. 一級緩存:是sqlSession對象的緩存,自帶的(不需要配置)不可卸載的(不想使用還不行);一級緩存的生命周期與sqlSession一致
  2. 二級緩存:是SqlSessionFactory的緩存,只要是同一個SqlSessionFactory創(chuàng)建的SqlSession就共享二級緩存的內(nèi)容,并且可以操作二級緩存。二級緩存如果要使用,需要手動開啟(需要配置)

7.1 一級緩存

  1. 查詢時,先去緩存中找;如果緩存中沒有,則從數(shù)據(jù)庫查詢用戶信息,然后將查詢到的信息存儲到緩存中;如果緩存中有,則直接從緩存中獲取信息。
  2. 清除一級緩存:
    1. sqlSession.commit(); // commit時清除一級緩存是為了避免數(shù)據(jù)發(fā)生增刪改后產(chǎn)生臟讀
    2. sqlSession.close();
    3. sqlSession.clearCache();
    4. 執(zhí)行增刪改操作

7.2 二級緩存

  1. 步驟1:在MyBatis核心配置文件中開啟二級緩存(由于默認開啟,因此這一步可省略)

    <!--cacheEnabled:
    true:開啟二級緩存(默認)
    false:不開啟二級緩存-->
    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
  2. 步驟2:在要使用二級緩存的SQL映射文件的mapper標簽里增加cache標簽

    <mapper namespace="com.liu2m.dao.UserDao">
        <cache></cache>
        <select id="findAll" resultType="com.liu2m.pojo.User">
            select * from user
        </select>
    </mapper>
    
  3. 步驟3:要使用二級緩存的POJO類必須實現(xiàn)Serializable接口

  • 只有當前的sqlSession.close()時,該sqlSession的數(shù)據(jù)才會存入二級緩存中
  • 執(zhí)行增刪改操作時會清除二級緩存
  • select標簽屬性:
    • flushCache默認為false
    • useCache默認為true
  • insert、delete、update標簽屬性:
    • flushCache默認為true
    • 沒有useCache屬性

8. MyBatis注解-

使用注解代替了SQL映射文件

@SelectKey():代替了selectKey標簽
@Insert("SQL"):代替了insert標簽
@Delete("SQL"):代替了delete標簽
@Update("SQL"):代替了update標簽
@Select("SQL"):代替了select標簽

@Results(id = "", value = @Result())
或@Results(@Result())
或@Results({@Result(), @Result()})
@Result(column = "列名", property = "屬性名", one = @One(select = "指定用來多表查詢的sqlmapper"), many = @Many(select = ""))
    @Results代替了resultMap標簽
    @Result代替了result標簽和id標簽
    @One()代替了association標簽
    @Many()代替了collection標簽

8.1 MyBatis注解實現(xiàn)基本CRUD

  1. 創(chuàng)建PojoDao接口:

    public interface UserDao {
        @SelectKey(keyProperty = "uid", keyColumn = "uid", before = false, statement = "select last_insert_id()", resultType = int.class)
        @Insert("INSERT INTO user VALUES (NULL, #{username}, #{password}, #{balance})")
        void addUser(User user);
    
        @Delete("delete from user where uid = #{id}")
        void deleteById(Integer id);
    
        @Update("update user set username = #{username}, password = #{password}, balance = #{balance} where uid = #{uid}")
        void updateUser(User user);
    
        @Select("select * from user")
        List<User> findAll();
    }
    
  2. 創(chuàng)建MyBatis核心配置文件

  3. 單元測試:

    @Test
    public void test01() {
        SqlSession sqlSession = SqlSessionFactoryUtils.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user = new User(2, "user4", "password4", 4000.0);
        userDao.addUser(user);
        System.out.println(user.getUid());
        userDao.deleteById(3);
        userDao.updateUser(user);
        userDao.findAll().stream().forEach(System.out::println);
        SqlSessionFactoryUtils.commitAndClose(sqlSession);
    }
    

8.2 MyBatis注解實現(xiàn)一對一映射及延遲加載

  1. 創(chuàng)建Account表的POJO:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Account implements Serializable {
        private Integer aid;
        private Double money;
        private Integer uid;
        /**
         * 表示一個Account對應一個User
         */
        private User user;
    }
    
  2. 創(chuàng)建PojoDao接口:

    public interface UserDao {
        @Select("select * from user where uid = #{id}")
        User findUserByUid(int uid);
    }
    
    public interface AccountDao {
        @Results(id = "accountUserMap", value = {
                @Result(column = "uid", property = "uid"),
                @Result(property = "user", one = @One(select = "com.liu2m.dao.UserDao.findUserByUid", fetchType = FetchType.LAZY), column = "uid")
        })
        @Select("select * from account where aid = #{aid}")
        Account findAccountByAid(int aid);
    }
    
  3. 單元測試:

    @Test
    public void test01() {
        SqlSession sqlSession = SqlSessionFactoryUtils.openSession();
        AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
        Account account = accountDao.findAccountByAid(1);
        System.out.println(account.getMoney());
    }
    

8.3 MyBatis注解實現(xiàn)一對多映射及延遲加載

  1. 創(chuàng)建User表的POJO:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User implements Serializable {
        private Integer uid;
        private String username;
        private String password;
        private Double balance;
        /**
         * 表示一個User對應多個Account
         */
        private List<Account> accountList;
    }
    
  2. 創(chuàng)建PojoDao接口:

    public interface UserDao {
        @Results(id = "userAccountMap", value = {
                @Result(column = "uid", property = "uid", id = true),
                @Result(property = "accountList", many = @Many(select = "com.liu2m.dao.AccountDao.findAccountListByUid", fetchType = FetchType.LAZY), column = "uid")
        })
        @Select("SELECT * FROM user WHERE uid = #{uid}")
        User findUserByUid(int uid);
    }
    
    public interface AccountDao {
        @Select("SELECT * FROM account WHERE uid = #{uid}")
        List<Account> findAccountListByUid(int uid);
    }
    
  3. 單元測試:

    @Test
    public void test01() {
        SqlSession sqlSession = SqlSessionFactoryUtils.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user = userDao.findUserByUid(1);
        System.out.println(user);
    }
    
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • MyBatis 1. 概述: MyBatis是一個基于Java持久層的框架,內(nèi)部封裝了JDBC,簡化了開發(fā)時對數(shù)據(jù)...
    青丶空閱讀 244評論 0 0
  • 目錄1 JDBC2 Mybaits概述3 Mybatis擴展4 Mybatis的加載和緩存5 Mybatis注解開...
    小小千千閱讀 625評論 0 2
  • 延遲加載策略 1、背景 問題:在一對多的關系中,當我們有一個用戶,它有100個賬戶。 在查詢用戶的時候,要不...
    魔笛使者閱讀 508評論 0 0
  • ### 入門案例 第一個:創(chuàng)建maven工程并導入坐標 第二個:創(chuàng)建實體類和dao接口 第三步:創(chuàng)建Mybatis...
    Sylvester_f7ee閱讀 238評論 0 0
  • 基于代理 Dao 實現(xiàn) CRUD 操作 1、查詢所有 mapper配置文件: select * from us...
    魔笛使者閱讀 157評論 0 0

友情鏈接更多精彩內(nèi)容