mybatis-day04-改進多表查詢collection配置、延遲加載、緩存、註解開發(★)

一、今日內容

1、mybatis延遲加載
2、mybatis緩存
3、mybatis註解開發

二、mybatis延遲加載

a、什麼是延遲加載

  1. 也叫懶加載
  2. 什麼時候需要,什麼時候去獲取 什麼時候需要該數據,什麼時候執行sql語句去查詢

b、一對一延遲加載

今日回顧一對一級聯查詢寫法,出錯點:
粗心錯誤:
     jdbc.properties變量名錯誤(模板忘記更新)
     AccountDao.xml的namespace忘記改成AccountDao的全名
     AccountDao.xml的路勁寫成了cn.ahpu.domain 應該是dao包下
     AccountDao.xml的resultMap寫成了resultType 映射時也有一個屬性名忘記改
day04視頻02很好的回顧

模板樣式代碼總結

mybatis-config.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>
    <!--引入外部配置文件-->
    <properties resource="jdbc.properties"></properties>
    <typeAliases>
        <!--掃描整個包 包下所有類取別名爲簡單類名(不區分大小寫)-->
        <package name="cn.ahpu.domain"></package>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <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>
    </environments>
    <mappers>
        <!--引入一個包 就引入了該包中所有接口或xml (動態代理前提也要求dao接口和xml包名一致)-->
        <package name="cn.ahpu.dao"></package>
    </mappers>
</configuration>

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatisdb_331
jdbc.username=root
jdbc.password=root

log4j.properties

# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE            debug   info   warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE, info
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=d:\axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n

回顧昨日一對一

Account和User一對一 查詢account同時得到對應唯一user
在這裏插入圖片描述
pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.ahpu</groupId>
    <artifactId>mybatis_day04_1_one2one_lazy</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
        </dependency>
    </dependencies>

</project>

User.java

public class User {
    private Integer id;
    private String username;
    private String password;
    private String sex;
    private String address;
    private Date birthday;
    //省略get/set
}

Account.java

public class Account {
    private Integer id;
    private String name;
    private Float money;
    private Integer u_id;
    //一個賬戶對應一個用戶
    private User user;
    //省略get/set
}

AccountDao.java

	// 查詢所有賬戶 賬戶包含唯一的用戶信息
    public List<Account> findAll();

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">
<!--
    動態代理 namesapce必須是dao接口全限定名
    mybatis-config.xml中配置了typeAliases 引入整個包 因而參數全限定名可以簡寫
-->
<mapper namespace="cn.ahpu.dao.AccountDao">
    <resultMap id="accounts" type="account">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="money" property="money"></result>
        <result column="u_id" property="u_id"></result>
        <!--配置唯一的user屬性-->
        <!--原來的配置-->
        <association property="user" javaType="User">
            <id column="uid" property="id"></id>
            <result column="uname" property="username"></result>
            <result column="password" property="password"></result>
            <result column="sex" property="sex"></result>
            <result column="address" property="address"></result>
            <result column="birthday" property="birthday"></result>
        </association>
    </resultMap>

    <select id="findAll" resultMap="accounts">
        select * from account,user where u_id=uid
    </select>

</mapper>

TestMybatisOne2OneLazy.java

@Test
    public void testFindAll(){
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(inputStream).openSession();
        AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
        List<Account> list = accountDao.findAll();
        for (Account account : list) {
            System.out.println(account);
        }
        sqlSession.close();
    }

改進一對一

Account裏只查詢Account表的信息,查詢User表的任務交給UserDao,Account裏引入下方法全名即可
每個xml裏只有本張表的映射配置了 完全隔離開了 不用怕多表查詢兩張表列名相同的衝突了
association 配法有變,加兩個屬性,具體字段便不用配置了
(Account表裏u_id,可以據此來)
UserDao

	//根據id查詢用戶
    public User findById(Integer id);

UserDao.xml

<mapper namespace="cn.ahpu.dao.UserDao">
    <resultMap id="usero" type="user">
    	<!--沒辦法 屬性名和列名不一致 必須配-->
        <id column="uid" property="id"></id>
        <result column="uname" property="username"></result>
    </resultMap>
    <select id="findById" parameterType="int" resultMap="usero">
        select * from user where uid=#{id}
    </select>
</mapper>

AccountDao.xml 改進配置

<mapper namespace="cn.ahpu.dao.AccountDao">
    <resultMap id="accounts" type="account">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="money" property="money"></result>
        <result column="u_id" property="u_id"></result>
        <!--配置唯一的user屬性-->       
        <!--現在改進的配置 新增配置column='外鍵'
            column="u_id":指定通過本表哪列查詢對應的用戶對象
            select:要執行的方法id 即: mapperId=namespace.id
              (看sql可知 user不是在此處查了)
        -->
        <association property="user" javaType="user" column="u_id" select="cn.ahpu.dao.UserDao.findById"></association>
    </resultMap>

    <select id="findAll" resultMap="accounts">
        select * from account
    </select>
</mapper>

一對一延遲加載配置(需要時再獲取 節省資源)

1.必須使用上面改進的association配置法
2.在association裏配置一個fetchType=“lazy”

<mapper namespace="cn.ahpu.dao.AccountDao">
    <resultMap id="accounts" type="account">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="money" property="money"></result>
        <result column="u_id" property="u_id"></result>
        <!--配置唯一的user屬性-->
		<!--fetchType="lazy": lazy:延遲加載   eager:立即加載-->
        <association property="user" javaType="user" column="u_id"
                     select="cn.ahpu.dao.UserDao.findById" fetchType="lazy"></association>
    </resultMap>
    <select id="findAll" resultMap="accounts">
        select * from account
    </select>
</mapper>

TestMybatisOne2OneLazy.java
測試方法裏不能直接調用toString了,因爲toString訪問所有屬性,一定會立即加載

@Test
    public void testFindAll(){
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(inputStream).openSession();
        AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
        List<Account> list = accountDao.findAll();
        for (Account account : list) {
            System.out.println(account.getName());
            System.out.println(account.getUser());
        }
        sqlSession.close();
    }

查看日誌 觀察延遲加載過程

1234564156
2020-01-09 13:00:41,295 1085   [           main] DEBUG   cn.ahpu.dao.UserDao.findById  - ==>  Preparing: select * from user where uid=? 
2020-01-09 13:00:41,296 1086   [           main] DEBUG   cn.ahpu.dao.UserDao.findById  - ==> Parameters: 1(Integer)
2020-01-09 13:00:41,307 1097   [           main] DEBUG   cn.ahpu.dao.UserDao.findById  - <==      Total: 1
User{id=1, username='zhangsan', password='zhangsan', sex='男', address='蜀山', birthday=Wed Jan 29 20:48:42 CST 2020}
242415
2020-01-09 13:00:41,311 1101   [           main] DEBUG   cn.ahpu.dao.UserDao.findById  - ==>  Preparing: select * from user where uid=? 
2020-01-09 13:00:41,311 1101   [           main] DEBUG   cn.ahpu.dao.UserDao.findById  - ==> Parameters: 2(Integer)
2020-01-09 13:00:41,314 1104   [           main] DEBUG   cn.ahpu.dao.UserDao.findById  - <==      Total: 1
User{id=2, username='lisi', password='lisi', sex='男', address='天墉城', birthday=Sat Feb 01 20:48:46 CST 2020}
24235698
2020-01-09 13:00:41,314 1104   [           main] DEBUG   cn.ahpu.dao.UserDao.findById  - ==>  Preparing: select * from user where uid=? 
2020-01-09 13:00:41,314 1104   [           main] DEBUG   cn.ahpu.dao.UserDao.findById  - ==> Parameters: 10(Integer)
2020-01-09 13:00:41,315 1105   [           main] DEBUG   cn.ahpu.dao.UserDao.findById  - <==      Total: 1
User{id=10, username='天河', password='tianhe', sex='男', address='青鸞峯', birthday=Tue Jan 07 16:15:23 CST 2020}
2020-01-09 13:00:41,315 1105   [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@544a2ea6]
2020-01-09 13:00:41,316 1106   [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@544a2ea6]
2020-01-09 13:00:41,316 1106   [           main] DEBUG source.pooled.PooledDataSource  - Returned connection 1414147750 to pool.

c、一對多延遲加載(也用改進的collection分離配置)

還是用戶對賬戶一對多 一個用戶對應多個賬戶 查詢所有用戶 同時查詢到每個用戶下的多個賬戶
改進的collection分離配置,同上面一對一的改進,account的查詢分離出來交給accountdao自己去查,userdao裏引入一下即可
Account.java 不再有User對象屬性
此處一對多一方寫集合 多方不寫對象

public class Account {
    private Integer id;
    private String name;
    private Float money;
    private Integer u_id;
}

User.java 加上Account集合
此處一對多一方寫集合 多方不寫對象

public class User {
    private Integer id;
    private String username;
    private String password;
    private String sex;
    private String address;
    private Date birthday;

    //一個用戶對應多個賬戶
    private List<Account> accounts;
}

UserDao.java

//查詢所有用戶:包含其下所有賬戶
    public List<User> findAll();

AccountDao.java

//根據主人用戶id(u_id=uid) 查詢所有賬戶
    public List<Account> findByUid(Integer uid);

AccountDao.xml

<mapper namespace="cn.ahpu.dao.AccountDao">
    <select id="findByUid" resultType="account" parameterType="int">
        select * from account where u_id=#{uid}
    </select>
</mapper>

UserDao.xml ★

<mapper namespace="cn.ahpu.dao.UserDao">
    <resultMap id="users" type="user">
        <id column="uid" property="id"></id>
        <result column="uname" property="username"></result>
        <result column="password" property="password"></result>
        <result column="sex" property="sex"></result>
        <result column="address" property="address"></result>
        <result column="birthday" property="birthday"></result>
        <!--映射 List<Account> accounts 改進寫法 account的查詢交給accountdao自己
            column:寫本表的uid屬性  (拿着本表uid去匹配account表的外鍵u_id)
            select:引入accountdao中對應的查詢方法
            fetchType="lazy":延遲加載
        -->
        <collection property="accounts" ofType="account" column="uid"
                    select="cn.ahpu.dao.AccountDao.findByUid" fetchType="lazy"></collection>
    </resultMap>

    <select id="findAll" resultMap="users">
        select * from user
    </select>
</mapper>

TestMybatisOne2ManyLazy 測試多對多改進collection配置與延遲加載

@Test
    public void testFindAll(){
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(inputStream).openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        List<User> list = userDao.findAll();
        for (User user : list) {
            System.out.println(user.getUsername());
            //System.out.println(user.getAccounts());//不寫就不會去查 account表
        }
        sqlSession.close();
    }

d、開啓全局的延遲加載

無需一個個在collection或association中配置fetchType=“lazy”,直接開啓全局懶加載

mybatis-config.xml 里加上全局配置

<!--核心配置文件全局設置-->
    <settings>
        <!--開啓全局延遲加載-->
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>

注意xml裏屬性的順序(,表示順序 ?表示1個或0個)

<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>

在這裏插入圖片描述

配置完成後collection裏的fetchType="lazy"可以刪了,測試仍然有延遲加載的效果

配置完畢後默認懶加載 若某個方法需要立即加載可以仍在collection或association中配置:fetchType=“eager”
進行立即加載

三、mybatis緩存

簡單理解:執行過的sql語句不會重複執行,直接到內存裏拿數據

a、一級緩存(不需要額外的配置或代碼 默認存在)

單表查詢所有user對象爲例 驗證一級緩存的存在
UserDao.java

public List<User> findAll();

UserDao.xml

<mapper namespace="cn.ahpu.dao.UserDao">
    <resultMap id="usero" type="user">
        <id column="uid" property="id"></id>
        <result column="uname" property="username"></result>
    </resultMap>
    <select id="findAll" resultMap="usero">
        select * from user
    </select>
</mapper>

TestMybatisCache 測試

/**
     * 一級緩存
     * 在同一sqlSession對象範圍下,兩次執行同一個sql語句,第二次沒有執行sql語句 說明一級緩存的存在
     *
     * 一級緩存執行流程
     *  第一次執行sql語句,查詢到數據,會在一級緩存中存儲sql語句和數據:以sql語句爲key,數據爲map存儲於map中
     *  則第二次執行sql時,會先從緩存中查詢,意sql語句爲key查詢,得到數據直接返回。若沒有相應key,則去查數據庫
     */
    @Test
    public void testFindAll(){
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml");
        //sqlSessionFactory 單例模式
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserDao userDao1 = sqlSession.getMapper(UserDao.class);
        List<User> list1 = userDao1.findAll();
        for (User user : list1) {
            System.out.println(user);
        }

        System.out.println("=====================================================");
        //兩次同一個sqlSession
        UserDao userdao2 = sqlSession.getMapper(UserDao.class);
        List<User> list2 = userdao2.findAll();
        for (User user : list2) {
            System.out.println(user);
        }

        sqlSession.close();
    }
2020-01-09 16:15:03,599 668    [           main] DEBUG source.pooled.PooledDataSource  - Created connection 428910174.
2020-01-09 16:15:03,599 668    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1990a65e]
2020-01-09 16:15:03,601 670    [           main] DEBUG    cn.ahpu.dao.UserDao.findAll  - ==>  Preparing: select * from user 
2020-01-09 16:15:03,687 756    [           main] DEBUG    cn.ahpu.dao.UserDao.findAll  - ==> Parameters: 
2020-01-09 16:15:03,719 788    [           main] DEBUG    cn.ahpu.dao.UserDao.findAll  - <==      Total: 5
User{id=1, username='zhangsan', password='zhangsan', sex='男', address='蜀山', birthday=Wed Jan 29 20:48:42 CST 2020}
User{id=2, username='lisi', password='lisi', sex='男', address='天墉城', birthday=Sat Feb 01 20:48:46 CST 2020}
User{id=10, username='天河', password='tianhe', sex='男', address='青鸞峯', birthday=Tue Jan 07 16:15:23 CST 2020}
User{id=17, username='jerry', password='jerry', sex='女', address='北京', birthday=Tue Jan 07 18:17:00 CST 2020}
User{id=18, username='小明', password='jerry', sex='男', address='北京', birthday=Tue Jan 07 23:18:39 CST 2020}
=====================================================
User{id=1, username='zhangsan', password='zhangsan', sex='男', address='蜀山', birthday=Wed Jan 29 20:48:42 CST 2020}
User{id=2, username='lisi', password='lisi', sex='男', address='天墉城', birthday=Sat Feb 01 20:48:46 CST 2020}
User{id=10, username='天河', password='tianhe', sex='男', address='青鸞峯', birthday=Tue Jan 07 16:15:23 CST 2020}
User{id=17, username='jerry', password='jerry', sex='女', address='北京', birthday=Tue Jan 07 18:17:00 CST 2020}
User{id=18, username='小明', password='jerry', sex='男', address='北京', birthday=Tue Jan 07 23:18:39 CST 2020}
2020-01-09 16:15:03,723 792    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1990a65e]
2020-01-09 16:15:03,725 794    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1990a65e]
2020-01-09 16:15:03,725 794    [           main] DEBUG source.pooled.PooledDataSource  - Returned connection 428910174 to pool.

執行增刪改與提交操作都會清空緩存

@Test
    public void testFindAll(){
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml");
        //sqlSessionFactory 單例模式
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserDao userDao1 = sqlSession.getMapper(UserDao.class);
        List<User> list1 = userDao1.findAll();
        for (User user : list1) {
            System.out.println(user);
        }
        //兩次同一個sqlSession 
        UserDao userdao2 = sqlSession.getMapper(UserDao.class);
        userdao2.delete(19);
        sqlSession.commit();
        //主動清空緩存
        //sqlSession.clearCache();
        System.out.println("=====================================================");
        //兩次同一個sqlSession
        UserDao userdao3 = sqlSession.getMapper(UserDao.class);
        List<User> list3 = userdao3.findAll();
        for (User user : list3) {
            System.out.println(user);
        }
        sqlSession.close();
    }
2020-01-09 16:10:24,150 1320   [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1990a65e]
2020-01-09 16:10:24,170 1340   [           main] DEBUG    cn.ahpu.dao.UserDao.findAll  - ==>  Preparing: select * from user 
2020-01-09 16:10:24,193 1363   [           main] DEBUG    cn.ahpu.dao.UserDao.findAll  - ==> Parameters: 
2020-01-09 16:10:24,236 1406   [           main] DEBUG    cn.ahpu.dao.UserDao.findAll  - <==      Total: 6
User{id=1, username='zhangsan', password='zhangsan', sex='男', address='蜀山', birthday=Wed Jan 29 20:48:42 CST 2020}
User{id=2, username='lisi', password='lisi', sex='男', address='天墉城', birthday=Sat Feb 01 20:48:46 CST 2020}
User{id=10, username='天河', password='tianhe', sex='男', address='青鸞峯', birthday=Tue Jan 07 16:15:23 CST 2020}
User{id=17, username='jerry', password='jerry', sex='女', address='北京', birthday=Tue Jan 07 18:17:00 CST 2020}
User{id=18, username='小明', password='jerry', sex='男', address='北京', birthday=Tue Jan 07 23:18:39 CST 2020}
User{id=19, username='小王', password='jerry', sex='男', address='北京', birthday=Tue Jan 07 23:18:58 CST 2020}
2020-01-09 16:10:24,251 1421   [           main] DEBUG     cn.ahpu.dao.UserDao.delete  - ==>  Preparing: delete from user where uid=? 
2020-01-09 16:10:24,251 1421   [           main] DEBUG     cn.ahpu.dao.UserDao.delete  - ==> Parameters: 19(Integer)
2020-01-09 16:10:24,271 1441   [           main] DEBUG     cn.ahpu.dao.UserDao.delete  - <==    Updates: 1
2020-01-09 16:10:24,271 1441   [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1990a65e]
=====================================================
2020-01-09 16:10:24,279 1449   [           main] DEBUG    cn.ahpu.dao.UserDao.findAll  - ==>  Preparing: select * from user 
2020-01-09 16:10:24,279 1449   [           main] DEBUG    cn.ahpu.dao.UserDao.findAll  - ==> Parameters: 
2020-01-09 16:10:24,285 1455   [           main] DEBUG    cn.ahpu.dao.UserDao.findAll  - <==      Total: 5
User{id=1, username='zhangsan', password='zhangsan', sex='男', address='蜀山', birthday=Wed Jan 29 20:48:42 CST 2020}
User{id=2, username='lisi', password='lisi', sex='男', address='天墉城', birthday=Sat Feb 01 20:48:46 CST 2020}
User{id=10, username='天河', password='tianhe', sex='男', address='青鸞峯', birthday=Tue Jan 07 16:15:23 CST 2020}
User{id=17, username='jerry', password='jerry', sex='女', address='北京', birthday=Tue Jan 07 18:17:00 CST 2020}
User{id=18, username='小明', password='jerry', sex='男', address='北京', birthday=Tue Jan 07 23:18:39 CST 2020}
2020-01-09 16:10:24,285 1455   [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1990a65e]
2020-01-09 16:10:24,286 1456   [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1990a65e]
2020-01-09 16:10:24,286 1456   [           main] DEBUG source.pooled.PooledDataSource  - Returned connection 428910174 to pool.

主動清空緩存:

  sqlSession.clearCache();

一級緩存
  1.在同一sqlSession對象範圍下,兩次執行同一個sql語句,第二次沒有執行sql語句 說明一級緩存的存在
  2.執行增刪改,提交操作(sqlSession.commit) 會清空緩存
  3.sqlSession.clearCache(); 清空緩存
  4.一級緩存是session級別的 兩個sqlSession之間是沒有一級緩存可言的(因而close是更換session,並不是簡單清空緩存)
一級緩存執行流程
  第一次執行sql語句,查詢到數據,會在一級緩存中存儲sql語句和數據:以sql語句爲key,數據爲map存儲於map中
  則第二次執行sql時,會先從緩存中查詢,意sql語句爲key查詢,得到數據直接返回。若沒有相應key,則去查數據庫

b.二級緩存(需要額外的修改代碼與加配置 基本不用)

二級緩存
  1.是sessionFactory級別的 工廠只有一個 也即:它可以在多個sqlSession對象之間共享緩存數據
  2.默認是開啓的
  3.在需要使用二級緩存的配置映射文件中開啓
  4.需要在二級緩存中保存的pojo對象必須實現序列化接口: User implements Serializable (說明二級緩存是寫到外存的)
  5.在同一個namespace範圍下,執行提交操作,會清空該namespace的緩存()
   eg:多表關聯查詢,第一次查詢A關聯得到B,然後B執行修改清空了緩存(或者說B的數據變了),A一直執行查詢
    A一直沒變,那麼namespaceA.sqlAB還在緩存中,B卻早已變了,查詢便出錯了 !巨大的坑!知道坑 用時小心!
   好在開發中二級緩存一般不用,都是自己手寫


工作流程
  1.在任意一個sqlSession對象中執行了查詢語句,當關閉了sqlSession對象時(說明必須關閉纔有緩存),在二級緩存中保存數據
    保存數據:以namespace.sql語句爲key值,以對象爲value進行存儲
  2.當其他sqlSession對象執行sql時,根據namespace.sql語句查詢緩存,命中直接取數據,否則纔去讀數據庫

仍然以查詢所有用戶爲例:
相對於上面的修改:
UserDao.xml
在這裏插入圖片描述
User.java
在這裏插入圖片描述
TestMybatisCache2 測試二級緩存的存在

/**
     * 二級緩存
     *  1.是sessionFactory級別的  工廠只有一個 也即:它可以在多個sqlSession對象之間共享緩存數據
     *  2.默認是開啓的
     *  3.在需要使用二級緩存的配置映射文件中開啓  <cache/>
     *  4.需要在二級緩存中保存的pojo對象必須實現序列化接口: User implements Serializable
     *
     * 二級緩存的工作流程
     *  1.在任意一個sqlSession對象中執行了查詢語句,當關閉了sqlSession對象時(說明必須關閉纔有緩存),在二級緩存中保存數據
     *    保存數據:以namespace.sql語句爲key值,以對象爲value進行存儲
     *  2.當其他sqlSession對象執行sql時,根據namespace.sql語句查詢緩存,命中直接取數據,否則纔去讀數據庫
     */
    @Test
    public void testFindAll(){
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml");
        //sqlSessionFactory 單例模式
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserDao userDao1 = sqlSession.getMapper(UserDao.class);
        List<User> list1 = userDao1.findAll();
        for (User user : list1) {
            System.out.println(user);
        }
        sqlSession.close();
        System.out.println("=====================================================");
        //兩次不同sqlSession
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        UserDao userdao2 = sqlSession2.getMapper(UserDao.class);
        List<User> list2 = userdao2.findAll();
        for (User user : list2) {
            System.out.println(user);
        }

        sqlSession.close();
    }

兩個session,一次select語句,還計算出了二級緩存命中率

2020-01-09 16:41:13,880 1788   [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@67b467e9]
2020-01-09 16:41:13,882 1790   [           main] DEBUG    cn.ahpu.dao.UserDao.findAll  - ==>  Preparing: select * from user 
2020-01-09 16:41:13,916 1824   [           main] DEBUG    cn.ahpu.dao.UserDao.findAll  - ==> Parameters: 
2020-01-09 16:41:13,972 1880   [           main] DEBUG    cn.ahpu.dao.UserDao.findAll  - <==      Total: 5
User{id=1, username='zhangsan', password='zhangsan', sex='男', address='蜀山', birthday=Wed Jan 29 20:48:42 CST 2020}
User{id=2, username='lisi', password='lisi', sex='男', address='天墉城', birthday=Sat Feb 01 20:48:46 CST 2020}
User{id=10, username='天河', password='tianhe', sex='男', address='青鸞峯', birthday=Tue Jan 07 16:15:23 CST 2020}
User{id=17, username='jerry', password='jerry', sex='女', address='北京', birthday=Tue Jan 07 18:17:00 CST 2020}
User{id=18, username='小明', password='jerry', sex='男', address='北京', birthday=Tue Jan 07 23:18:39 CST 2020}
2020-01-09 16:41:14,042 1950   [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@67b467e9]
2020-01-09 16:41:14,043 1951   [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@67b467e9]
2020-01-09 16:41:14,043 1951   [           main] DEBUG source.pooled.PooledDataSource  - Returned connection 1739876329 to pool.
=====================================================
2020-01-09 16:41:14,112 2020   [           main] DEBUG            cn.ahpu.dao.UserDao  - Cache Hit Ratio [cn.ahpu.dao.UserDao]: 0.5(緩存命中率0.5)
User{id=1, username='zhangsan', password='zhangsan', sex='男', address='蜀山', birthday=Wed Jan 29 20:48:42 CST 2020}
User{id=2, username='lisi', password='lisi', sex='男', address='天墉城', birthday=Sat Feb 01 20:48:46 CST 2020}
User{id=10, username='天河', password='tianhe', sex='男', address='青鸞峯', birthday=Tue Jan 07 16:15:23 CST 2020}
User{id=17, username='jerry', password='jerry', sex='女', address='北京', birthday=Tue Jan 07 18:17:00 CST 2020}
User{id=18, username='小明', password='jerry', sex='男', address='北京', birthday=Tue Jan 07 23:18:39 CST 2020}

四、mybatis註解開發(★★ 簡單到懷疑人生 今後寫法)

a、實現增刪改查

連xxxDao.xml都沒有了,只有UserDao接口+註解和測試類了
沒有dao對應的配置文件了‘’
在這裏插入圖片描述

根目錄下3個配置文件和User.java 和之前一樣固定不變
只用寫UserDao接口和TestMtbatisCRUD測試類

mybatis-config.xml 核心配置文件中的取別名也可以刪了:
在這裏插入圖片描述

UserDao.java

package cn.ahpu.dao;

import cn.ahpu.domain.User;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import java.util.List;

/**
 * @author 寒面銀槍
 * @create 2020-01-09 17:18
 */
public interface UserDao {

    /**
     * 查詢所有用戶
     * @return
     */
    @Select("select * from user")
    public List<User> findAll();

    /**
     * 根據id查詢
     * @return
     */
    @Select("select * from user where uid=#{id}")
    public User findById(Integer id);

    /**
     * 根據姓名模糊查詢
     * @param username
     * @return
     */
    //此處寫的sql語句內只能是雙引號 不能寫單引號
    @Select("select * from user where uname like \"%\"#{username}\"%\"")
    public List<User> findByUsername(String username);

    /**
     * 查詢總記錄數
     * @return
     */
    @Select("select count(*) from user")
    public Integer findTotalCount();

    /**
     * 添加用戶
     * @param user
     */
    @Insert("insert into user values(null,#{username},#{password},#{sex},#{address},#{birthday})")
    public void insert(User user);

    /**
     * 更新用戶
     * @param user
     */
    @Update("update user set uname=#{username},password=#{password},sex=#{sex}," +
            "address=#{address},birthday=#{birthday} where uid=#{id}")
    public void update(User user);

    /**
     * 根據id刪除用戶
     * @param id
     */
    @Delete("delete from user where uid=#{id}")
    public void delete(Integer id);

}

TestMtbatisCRUD

package cn.ahpu;

import cn.ahpu.dao.UserDao;
import cn.ahpu.domain.User;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.InputStream;
import java.util.Date;
import java.util.List;

/**
 * @author 寒面銀槍
 * @create 2020-01-09 18:19
 */
public class TestMtbatisCRUD {

    @Test
    public void testFindAll(){
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        List<User> list = userDao.findAll();
        for (User user : list) {
            System.out.println(user);
        }
        sqlSession.close();
    }

    @Test
    public void testFindById(){
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user = userDao.findById(18);
        System.out.println(user);
        sqlSession.close();
    }

    @Test
    public void testFindByUsername(){
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        List<User> list = userDao.findByUsername("s");
        for (User user : list) {
            System.out.println(user);
        }
        sqlSession.close();
    }

    @Test
    public void testFindTotalCount(){
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        Integer totalCount = userDao.findTotalCount();
        System.out.println(totalCount);
        sqlSession.close();
    }

    @Test
    public void testInsert(){
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user = new User();
        user.setUsername("mayun");user.setAddress("杭州");user.setSex("男");
        user.setPassword("123");user.setBirthday(new Date());
        userDao.insert(user);
        sqlSession.commit();
        sqlSession.close();
    }
}

註解配置結果集映射

表列名與類屬性名不同,即結果集映射,也用註解完成

/**
     * 查詢所有用戶
     * @return
     *
     * @Results:映射結果集
     * @Result:映射表列名和屬性名不一樣的列
     *      id=true:表示id是主鍵
     * 註解中只需要寫:列名與屬性名不一致的即可 (不像xml中那樣有時需要全寫)(唯一特殊見四、b、結果集映射特殊情況 用超過1次就必須配置)
     */

    @Select("select * from user")
    @Results({
            @Result(id = true, column = "uid", property = "id"),
            @Result(column = "uname", property = "username")
    })
    public List<User> findAll();

主鍵回顯(執行插入後未提交事務前獲取插入記錄的主鍵)

mysql中獲取主鍵無意義 oracle中有作用

註解方式:

/**
     * 添加用戶
     * @param user
     */
    //保存後獲取主鍵  (執行insert代碼後就可以獲取主鍵)
    //keyProperty:主鍵屬性名  keyColumn:主鍵列名  resultType:主鍵類型
    // before=false:非添加之前查詢,也即是添加之後可以查詢主鍵
    // before=true:添加之前可查詢主鍵 (可惜mysql不支持 oracle支持)
    //statement:需要執行的sql語句
    //select last_insert_id():最後一次執行添加生成的主鍵 (也即是要查詢的主鍵)
    @SelectKey(keyProperty = "id",keyColumn = "uid",resultType = Integer.class,before = false,
    statement = "select last_insert_id()")
    @Insert("insert into user values(null,#{username},#{password},#{sex},#{address},#{birthday})")
    public void insert(User user);

xml方式

<insert id="save" parameterType="user">
	<selectKey keyColumn="uid" keyProperty="id" resultType="int" order="AFTER">
		select last_insert_id()
	</selectKey>
	insert into user values(null,#{username},#{password},#{sex},#{address},#{birthday})
</insert>

TestMtbatisCRUD 測試代碼:

@Test
    public void testInsert(){
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user = new User();
        user.setUsername("mayun");user.setAddress("杭州");user.setSex("男");
        user.setPassword("123");user.setBirthday(new Date());
        userDao.insert(user);
        //不加註解此處獲取不了主鍵  (爲何commit之前 注意3大框架整合後不能輕易提交 一提交就不能再用session 萬一延遲加載 有些數據就永遠丟了)
        System.out.println(user.getId());
        sqlSession.commit();
        sqlSession.close();
    }

b、實現一對一

賬戶與用戶一對一 查詢賬戶同時查詢其唯一對應的用戶
domain:
User.java

public class User {
    private Integer id;
    private String username;
    private String password;
    private String sex;
    private String address;
    private Date birthday;
}

Account.java

public class Account {
    private Integer id;
    private String name;
    private Float money;
    private Integer u_id;
    //一個賬戶對應一個用戶
    private User user;
}

UserDao.java

public interface UserDao {
    //一對一 account那裏傳過來的就是user主鍵
    @Select("select * from user where uid=#{id}")
    //可憐表列名還是與pojo屬性名不一致
    @Results({
            @Result(id=true,property = "id",column = "uid"),
            @Result(property = "username",column = "uname")
    })
    public User findById(Integer id);
}

AccountDao.java ★ 註解

public interface AccountDao {
    /**
     * 查詢所有賬戶 包含對應唯一用戶
     * @return
     */
    @Select("select * from account")
    //屬性與列不一致就得寫結果映射 user肯定沒有匹配列 自然要寫結果映射
    //one=@One(select = "",fetchType="") 屬性是單個對應
    //many:屬性是多個對應(一對多中的一方寫這個)
    //fetchType = FetchType.LAZY:提取方式爲延遲加載 注意位置 (也可以直接在mybatis-config.xml中配置全局的)
    @Results({
            @Result(property = "user",column = "u_id",javaType = User.class,
            one=@One(select = "cn.ahpu.dao.UserDao.findById",fetchType = FetchType.LAZY))
    })
    public List<Account> findAll();
}

TestMybatisAnnoOne2One 測試類

public class TestMybatisAnnoOne2One {

    @Test
    public void testFindAll(){
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(inputStream).openSession();
        AccountDao accountDao = sqlSession.getMapper(AccountDao.class);

        List<Account> list = accountDao.findAll();
        for (Account account : list) {
            System.out.println(account.getName()+"|"+account.getMoney()+"元");
            System.out.println(account.getUser());
        }

        sqlSession.close();
    }
}

結果集映射特殊情況

之前說過,註解開發只用寫列名和屬性名不同的映射,其他不用寫,但若一個列被引用了兩次或兩次以上,則兩次映射都必須寫出來
eg:上面的一對多關聯查詢,外鍵u_id作爲查詢user的參數引用了一次,那麼再在測試代碼中打印輸出account.u_id屬性值就爲null了,除非再加個配置 (u_id可以傳遞參數了 不能輕易刪除 不過java代碼內確實用不到它)

測試代碼輸入u_id
在這裏插入圖片描述
1.AccountDao.java沒有單獨配置u_id映射
在這裏插入圖片描述
2.AccountDao.java中配置u_id映射
在這裏插入圖片描述
在這裏插入圖片描述

c、實現一對多

一個用戶對應多個賬戶 查詢用戶同時要包含其下所有的賬戶
Account.java

public class Account {
    private Integer id;
    private String name;
    private Float money;
    private Integer u_id;

User.java

public class User {
    private Integer id;
    private String username;
    private String password;
    private String sex;
    private String address;
    private Date birthday;

    private List<Account> accounts;
}

AccountDao.java

public interface AccountDao {
    /**
     * 根據u_id查詢賬戶
     * @param uid
     * @return
     * #{}此處也可以隨便寫
     */
    @Select("select * from account where u_id=#{uid}")
    public List<Account> findByUid(Integer uid);
}

UserDao.java ★

1.List屬性的類型可以省略不配 要配也只能配javaType = List.class, 此處,沒有ofType
2.延遲加載同樣也可以直接配置一個全局的

public interface UserDao {
    /**
     * 查詢所有用戶 包含每個用戶下的所有多個賬戶
     * @return
     */
    @Select("select * from user")
    @Results({
            @Result(id=true,property = "id",column = "uid"),
            @Result(property = "username",column = "uname"),
            //特殊兩處: 1.xml內寫ofType 此處只有javaType就只能配javaType了
            //          2.javaType不能list中單個元素的類型 直接寫集合類型List.class
            //但其實此處可以省略不配(2個特殊點就都不用記憶了)  下面的javaType = List.class,可以直接刪掉 ^_^
            @Result(property = "accounts",column = "uid",javaType = List.class,
            many = @Many(select = "cn.ahpu.dao.AccountDao.findByUid",fetchType = FetchType.LAZY))
    })
    public List<User> findAll();
}

d、動態sql

單表查詢user 姓名模糊查詢 性別等於查詢

動態sql語句太複雜,不可能直接在註解上寫if for等邏輯了,mybatis的處理是提供新標籤:
@SelectProvider(type=UserSqlProvider.class, method=“findByCondition”)
也即是傳入一個類的方法,返回值爲String字符串,也就是讓你自己在java代碼邏輯內拼接好sql傳回來
好處:多複雜的sql語句都不用怕了,java字符串拼接對程序員來說太簡單了

在這裏插入圖片描述

User.java

public class User {
    private Integer id;
    private String username;
    private String password;
    private String sex;
    private String address;
    private Date birthday;
}

UserDao.java 註解★

public interface UserDao {

    /**
     * 根據姓名模糊查詢,性別等於查詢
     * @return
     */

    //@Select("select * from user where uname like \"%\"#{username}\"%\" and sex=#{sex}")
    //一個字符串內寫if for語句不大現實 換用新的標籤
    // SelectProvider: sql語句提供者
    //  屬性; type:sql語句提供者的類字節碼(.class)
    //         method:提供類中的方法

    @SelectProvider(type=UserSqlProvider.class, method="findByCondition")
    @Results({
            @Result(id = true, column = "uid", property = "id"),
            @Result(column = "uname", property = "username")
    })
    public List<User> findByCondition(User user);
}

UserSqlProvider.java ☆

public class UserSqlProvider {
    //方法名和提供對象取得一致吧 好對應    參數user是dao中方法的參數 直接寫 會自動傳進來的
    public String findByCondition(User user){
        //經常修改用String不合適 要想線程安全也必須用StringBuffer
        StringBuffer sb=new StringBuffer();
        sb.append("select * from user where 1=1 ");//注意空格
        if(user.getUsername()!=null){
            sb.append(" and uname like \"%\"#{username}\"%\" ");
        }
        if(user.getSex()!=null){
            sb.append(" and sex=#{sex} ");
        }
        return sb.toString();
        //在這裏拼接sql無論多複雜都能做好了 java代碼字符串拼接太熟悉了
        //唯一注意拼接時的空格 最好前後都多加空格
    }
}

TestMtbatisCRUD 測試方法類

@Test
    public void testFindByCondition(){
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user = new User();
        //user.setUsername("s");
        user.setSex("男");
        List<User> list = userDao.findByCondition(user);
        for (User u : list) {
            System.out.println(u);
        }
        sqlSession.close();
    }

mybatis 四天總結

1. mybatis 第一天 
	自定義框架
	mybaits 的入門
2. mybatis 第二天
	CRUD : selectList ,selectOne ,insert ,update ,delete
		#{ } 
		${ }
	參數類型:簡單類型: ${ value} #{隨便}
		pojo類型:${屬性名} #{屬性名}
		複雜類型:
		map: #{key}
		多參數:#{param1} , #{param2}....
	傳統模式開發: UserDaoImpl implements UserDao
	動態代理開發:UserDao
	核心配置文件:properties , typeAliaes , mappers ,settings(延遲加載,二級緩存, 日誌)
	返回值類型:
3. mybatis第三天
	數據源:type=POOLED(自帶的以後不會用) --- c3p0 ,dbcp ,spring jdbc, druid(阿里出品數據源)
	事務:openSession(true | false)
	動態的sql語句:if , where , sql片段 , foreach
	一對一映射:
	一對多映射:
	多對多映射:
4. mybatis第四天
	延遲加載(以後都用) -- 按需加載
	一級緩存: session級別的,
	二級緩存:sessionFactory,應用級別的(漏洞 基本不用)
	註解開發: ()
	
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章