學習Mybatis

1.Mybatis簡介

  • 學習視頻:https://www.bilibili.com/vide...
  • 持久化:就是將程序的數據在持久狀態和瞬時狀態轉化的過程
  • 持久層:完成持久化工做的代碼塊,統稱爲Dao層
  • MyBatis:簡化數據庫鏈接和操做數據庫的操做的半持久框架,是目前的主流
  • 官網:https://mybatis.org/mybatis-3...

2.第一個Mybatis項目

2.1.配置數據庫

= 建立數據庫,表,表數據html

CREATE TABLE `user` (
        `id` int(20) NOT NULL AUTO_INCREMENT,#id不爲0,自增
        `name` varchar(30) DEFAULT NULL,#name默認null
        `pwd` varchar(30) DEFAULT NULL,#pwd默認null
        PRIMARY KEY (`id`)#主鍵索引=id
)ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `user` VALUES(1,'狂神','123456');
INSERT INTO `user` VALUES(2,'張三','123456');
INSERT INTO `user` VALUES(3,'李四','123456');

2.2.搭建環境

  • 父工程導包

<dependency>
    <!--mysql驅動-->
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.48</version>
</dependency>
<dependency>
    <!--mybatis官網-->
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.2</version>
</dependency>
<dependency>
    <!-- junit測試-->
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
  • 子工程加載資源過濾器

防止後綴爲properties和xml配置文件沒法加載java

<!--資源過濾器,防止導入資源失敗問題,最好在父子pom.xml裏都加入一下代碼-->
<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>
  • 配置resources /mybais-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>
    <!--數據源-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!--鏈接數據庫,所有都要被下來-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="admin"/>
            </dataSource>
        </environment>
    </environments>
    <!--mapper注入mybatis-->
    <mappers>
        <mapper resource="com/ssl/dao/UserMapper.xml"/>
    </mappers>
</configuration>
  • 編寫工廠工具類

    • spring整合mybatis後,這個操做再mybats-config.xml中配置
public class MyBatisUtil {
    /**
     * 提高sqlSessionFactory做用域,便於全局使用
     */
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            /*
            使用Mybatis第一步,獲取sqlSessionFactory對象
             */
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * sqlSessionFactory對象獲取SQLSession實例
     */
    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}

2.3.Dao層

  • Pojo:User
  • Dao:接口;實現類變成了XXXMapper.xml文件
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">
<!--namespace命名空間=要實現的dao接口-->
<mapper namespace="com.ssl.dao.UserDao">
    <select id="getUserList" resultType="com.ssl.pojo.User">
        select * from mybatis.user
    </select>
</mapper>

2.4 測試

  • 資源過濾異常,見上面的子工程配置資源過濾器
  • mapper註冊失敗異常:在mybatis-config.xml配置
<mappers>
    <mapper resource="com/ssl/dao/UserMapper.xml"/>
</mappers>
public class UserMapperTest {
    @Test
    public void getUserList() {
        //1 獲取是sqlSession對象
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        //2 獲取的是接口的.class,由於多態
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = userMapper.getUserList();
        for (User user : userList) {
            System.out.println(user);
        }
        //3 建議:最後關閉sqlSession
        sqlSession.close();
    }
}

3. CRUD

3.1 namespace=接口全類名相同

<mapper namespace="com.ssl.dao.UserMapper">
    <select id="getUserList" resultType="com.ssl.pojo.User">
        select * from mybatis.user
    </select>
</mapper>

3.2 select

  • id:接口中的方法名
  • parameterType:返回結果
  • resultType:方法參數

3.3 insert/update/delete

  • 增刪改須要添加事務,返回值只有int,不用添加resultType
<mapper namespace="com.ssl.dao.UserMapper">
    <!--增刪改須要提交事務,沒有指定的返回值,都是int,因此不用添加resultType-->
    <!--添加一個用戶,對象中的屬性能夠直接取出來-->
    <insert id="addUser" parameterType="com.ssl.pojo.User">
        INSERT INTO mybatis.user(id,NAME,pwd) VALUES (#{id},#{name},#{pwd});
    </insert>
    <update id="updateUser" parameterType="com.ssl.pojo.User">
        update mybatis.user set name = #{name},pwd=#{pwd} where id=#{id};
    </update>
    <delete id="deleteUserById" parameterType="int">
        delete from mybatis.user where id=#{id}
    </delete>
</mapper>

3.4 常見錯誤

  • 增刪改sql語句寫錯
# 增長鬚要values 
insert into mybatis.user(id,NAME,pwd) VALUES (#{id},#{name},#{pwd});
# 修改須要set
update mybatis.user set name = #{name},pwd=#{pwd} where id=#{id};
# 刪除須要from
delete from mybatis.user where id=#{id}
  • 出現bug,是從後往前看查看緣由

3.5 萬能map

  • 若是數據庫字段太多,添加修改須要的bean太多,使用map來封裝參數,
  • 好處一:避免多餘代碼
  • 好處二:跳過特定的Bean屬性,能夠隨意命名key,保證value是字段的屬性就行

3.6 模糊查詢

<!--模糊查詢1 不推薦-->
<select id="getLikeUser1" parameterType="string" resultType="com.ssl.pojo.User">
    select * from mybatis.user where name like #{value};
</select>
<!--模糊查詢2 建議寫死-->
<select id="getLikeUser2" parameterType="string" resultType="com.ssl.pojo.User">
    select * from mybatis.user where name like "%"#{value}"%";
</select>

4 配置解析

4.1 mybatis_config.xml

  • 在resource中建立mybatis_config.xml

4.2 mybatis中的配置屬性

  • properties(屬性)
  • settings(設置)
  • typeAliases(類型別名)
  • typeHandlers(類型處理器)
  • objectFactory(對象工廠)
  • plugins(插件)
  • environments(環境配置)
  • environment(環境變量)
  • transactionManager(事務管理器)
  • dataSource(數據源)
  • databaseIdProvider(數據庫廠商標識)
  • mappers(映射器)

4.3 enviroments(環境)

  1. 事務管理器:transcationManager,默認「JDBC」
  2. 鏈接數據庫:默認pooled
<environments default="development">
        <environment id="development">
            <!--事務管理器:默認JDBC-->
            <transactionManager type="JDBC"/>
            <!--鏈接數據源:默認POOLED-->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
</environments>

4.4 properties(屬性)

  • db.properies
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf-8
username=root
password=123456

4.5 settings(設置)

settings配置 解釋說明 默認狀態
cacheEnabled(重要) 全局性地開啓或關閉全部映射器配置文件中已配置的任何緩存。 默認開啓
lazyLoadingEnabled 延遲加載的全局開關。當開啓時,全部關聯對象都會延遲加載。 特定關聯關係中可經過設置 fetchType 屬性來覆蓋該項的開關狀態。 默認關閉
mapUnderscoreToCamelCase(重要) 是否開啓駝峯命名自動映射,即從經典數據庫列名 A_COLUMN 映射到經典 Java 屬性名 aColumn。 默認關閉
logImpl(最重要) 指定 MyBatis 所用日誌的具體實現,未指定時將自動查找。 SLF4J、LOG4J、STDOUT_LOGGING等,默認關閉

4.6 typeAliases(類型別名)

  • 做用:mapper.xml配置resultType時,簡化書寫

<!--給各類類取別名,簡化使用配置-->
<typeAliases>
    <!--方式一:指定類
        <typeAlias alias="User" type="com.ssl.pojo.User"/>
        -->
    <!--方式二;指定包,包中的小寫做爲別名
                    也能夠更改小寫名,在類上使用@value(「別名」)
        -->
    <package name="com.ssl.pojo"/>
</typeAliases>

4.7 plugins(插件)

後期須要加深學習的兩大插件,使Mybatis配置更加簡單mysql

  • Mybatis-Plus
  • MyBatis Generator Core

4.8 mappers(映射器)

  1. 在mybatis-config.xml中配置mapper映射器
<!-- 使用resource相對於類路徑的資源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
  1. class和package綁定:接口和mapper配置文件必須同名,是否必須在同一個包下有待學習?
<!-- 使用class映射器接口實現類的徹底限定類名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 使用package+name將包內的映射器接口實現所有註冊爲映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

4.9 MyBatisUtil(參數做用域)

名稱 解釋說明
SqlSessionFactoryBuilder 一旦建立就再也不須要它了,做用域是局部變量=靜態代碼塊先加載
SqlSessionFactory 運行期間一直存在,做用域是應用做用域,使用單例模式或者靜態單例模式。
SqlSession 鏈接到數據庫的請求,線程不安全,用完後立刻關閉;做用域是方法或者請求中,用完就關閉,關閉操做十分重要
public class MyBatisUtil {
    //0 提高第三步中sqlSessionFactory做用域
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            //1 獲取mybatis配置文件
            String resource = "mybatis-config.xml";
            //2 獲取配置文件的輸入流
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //3 使用SqlSessionFactoryBuilder().build()建立sqlSessionFactory
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //4 經過getSqlSession獲取session
    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession(true);
    }
}

5 屬性名和字段名不一致

5.1 更改User中的pwd->password

  • 模擬實現這個過程,數據庫中是pwd,更改Pojo中的User屬性爲pwd,致使不一致
//查詢結果:不一致的字段查詢結果爲null
User{id=1, name='狂神', password='null'}

5.2 解決辦法

  • 更改pojo成員屬性名 = 數據庫字段名:使用起來太low,不推薦
  • 使用結果集映射=resultMap:哪一個字段不一致,就使不一致的成員屬性property映射到數據庫的column

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace命名空間=要實現的dao接口=原來的dao實現類-->
<mapper namespace="com.ssl.dao.UserMapper">
    <!--更改結果集映射,解決屬性名和數據庫列名不一致-->
    <resultMap id="UserMap" type="com.ssl.pojo.User">
        <result column="pwd" property="password"/>
    </resultMap>
    <!--經過id查詢一個用戶-->
    <select id="getUserById" parameterType="int" resultMap="UserMap">
            select * from mybatis.user where id = #{id};
    </select>
</mapper>

6 開啓日誌

6.1 日誌工廠

  • 在setting中配置:name = logImpl 大小寫和空格不能錯
  • LOG4J:必須掌握,步驟:setting配置,導包,配置pro,測試時加載面試

    • setting配置,+導依賴包算法

      <settings>
             <setting name="logImpl" value="log4j"/>
       </settings>
      <!--log4j日誌-->
      <dependency>
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
          <version>1.2.17</version>
      </dependency>
    • log4j.properties:須要時去網上找一份spring

      # log4j日誌系統:通用配置
      # Define the root logger with appender file
      # log=D:\logs
      log4j.rootLogger = DEBUG, FILE, console
      # 輸出到當前目錄文件下的log包中
      log4j.appender.FILE=org.apache.log4j.FileAppender
      log4j.appender.FILE.File=./logs/log4j.log
      # Set the immediate flush to true (default)
      log4j.appender.FILE.ImmediateFlush=true
      # Set the threshold to debug mode
      log4j.appender.FILE.Threshold=debug
      # Set the threshold to debug mode
      # 設置日誌信息追加
      log4j.appender.FILE.Append=true
      # Set the maximum file size before rollover
      # 30MB
      log4j.appender.FILE.MaxFileSize=5KB
      # Set the backup index
      log4j.appender.FILE.MaxBackupIndex=2
      # Define the layout for file appender
      log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
      log4j.appender.FILE.layout.conversionPattern=%m%n
      # 將日誌輸出到控制檯
      log4j.appender.console=org.apache.log4j.ConsoleAppender
      log4j.appender.console.layout=org.apache.log4j.PatternLayout
      log4j.appender.console.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}]-[%t]-[%F:%L]-[%p]-[%c]-%m%n
      #log4j.appender.console.layout.ConversionPattern=[%d{yyyy-MM-dd}]-[%t]-[%x]-[%-5p]-[%-10c:%m%n]
      log4j.appender.console.encoding=UTF-8
    • test中:static Logger logger = Logger.getLogger(UserMapperTest.class);sql

      public class UserMapperTest {
          //使用log4j
          static Logger logger = Logger.getLogger(UserMapperTest.class);
      
          @Test
          public void getUserById() {
              //1 獲取是sqlSession對象
              SqlSession sqlSession = MyBatisUtil.getSqlSession();
              //2 獲取方式一:getMapper
              UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
              User user = userMapper.getUserById(1);
              System.out.println(user);
              //3 建議:最後關閉sqlSession
              sqlSession.close();
          }
      }
  • STDOUT_LOGGING :掌握,不用導包,mybatis默認配置了,缺點就是隻在控制檯顯示
<settings>
       <!-- <setting name="logImpl" value="log4j"/>-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
 </settings>
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • SLF4J
  • NO_LOGGING

7 分頁

7.1 limit

  • 第一個參數:startIndex=開始分頁的下標 = (第幾頁 - 1 )* pageSize
  • 第二個參數:pageSize=分頁中每頁的大小
  • 只有一個參數:默認是從第一個元數到該index下標實現分頁
select * from user limit startIndex,pageSize;# startIndex=(第幾頁-1)*pageSize
select * from user limit index;# 默認從第一個元素到第index個用戶

7.2 實現分頁

  • map和RowbBounds二者均可以實現分頁:推薦使用map分頁,由於默認key=#{key}
public interface UserMapper {
    /**
     * 根據id獲取一個用戶
     * @param id 指定
     * @return User
     */
    User getUserById(int id);

    /**
     * 經過map分頁數據,推薦使用
     * @param map 經常使用
     * @return ListUser
     */
    List<User> getLimitUser(Map<String,Object> map);

    /**
     * 瞭解,不推薦使用,經過RowBounds分頁數據
     * @return ListUser
     */
    List<User> getLimitUserByRowBounds();
}
<?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.ssl.dao.UserMapper">
    <!--更改結果集映射,解決屬性名和數據庫列名不一致-->
    <resultMap id="UserMap" type="com.ssl.pojo.User">
    <result column="pwd" property="password"/>
    </resultMap>
    <!--map分頁-->
    <select id="getLimitUser" parameterType="map" resultMap="UserMap">
         select * from mybatis.user limit #{startIndex},#{pageSize};
    </select>
    <!--RowBounds分頁-->
    <select id="getLimitUserByRowBounds" resultMap="UserMap">
         select * from mybatis.user ;
    </select>
</mapper>
public class UserMapperTest {    
    @Test
    public void getLimitUser() {
        UserMapper userMapper = MyBatisUtil.getSqlSession().getMapper(UserMapper.class);
        Map<String, Object> map = new HashMap<>();
        //mapper.xml會自動尋找map中的key=#{key}
        map.put("startIndex",0);
        map.put("pageSize",2);
        List<User> limitUser = userMapper.getLimitUser(map);
        for (User user : limitUser) {
            System.out.println(user);
        }
    }
     @Test
    public void getLimitUserBrRowBounds() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        //老式的查詢分頁:使用RowBounds
        RowBounds rowBounds = new RowBounds(0, 2);
        //語法麻煩
        List<User> usersList = sqlSession.selectList("com.ssl.dao.UserMapper.getLimitUserByRowBounds",0,rowBounds);
        for (User user : usersList) {
            System.out.println(user);
        }
    }
}

7.3 分頁插件

  • 自學Mybatis PageHelper插件,公司須要就去網站自學

8 註解配置sql語句

8.1 mybaits-config.xml中配置映射器

<!--綁定註解開發的接口 class-->
<mappers>
    <mapper class="com.ssl.dao.UserMapper"/>
</mappers>

8.2 缺點

  • 若是表中列名和成員屬性名不一致,查出來就是null

8.3 註解的CRUD

  • 學習@param(很是重要)數據庫

    • 基本類型、String建議都加上,引用類型不用加
    • (uid)中的就是sql中的#{uid}
public interface UserMapper {
    /**
     * 使用註解開發,有侷限性就是column 必須與 dao接口成員屬性名一致,否知輸出就是null,查不出來
     * 因此註解語句開發,便於使用簡單場景
     */
    @Select("select * from user")
    List<User> getUsers();

    /**
     * @param id=uid
     * @return User
     */
    @Select("select * from user where id = #{uid}")
    User getUserById(@Param("uid") int id);
}
  • 增刪改自動提交事務
public static SqlSession getSqlSession() {
        //不推薦使用,建議手動提交commit
        return sqlSessionFactory.openSession(true);
    }

9 Lombok

9.1 IDEA中安裝Lombok插件

9.2 maven安裝依賴

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
</dependency>

9.3 @Data等註解

  • @Data:最經常使用,自動加上Setter Getter equals tostring,
  • @AllArgsConstructor:有參構造
  • @NoArgsConstructor:無參構造

9.4 缺點

  • 雖然能夠混合使用,但多重的構造器的構造器不能重載
  • 公司用就用,不用就少用,由於改變了java源碼的書寫習慣,不利於推廣

10 多對一

  • 導入Lombok插件和依賴,減小pojo代碼
  • 新建實體類Student、Teacher和數據庫表apache

    • Student中有一個字段tid使用外鍵約束,關聯Teacher
CREATE TABLE `student` (
  `id` int(10) NOT NULL,
  `name` varchar(20) 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

CREATE TABLE `teacher` (
  `id` int(10) NOT NULL,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
  • 配置環境:pojo和mapper接口
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private Integer id;
    private String name;
    //須要關聯一個老師類
    private Teacher teacher;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private int id;
    private String name;
}
public interface StudentMapper {
    //子查詢
    List<Student> getStudents();
    //聯表查詢
    List<Student> getStudents1();
}

10.1:子查詢=查詢嵌套

  • mapper.xml編程

    • 如下對於理解很是重要
    • javaType=「teacher」配置了別名,大小寫均可以,去pojo路徑中中找Teacher,而後使用其成員屬性tid

<!--方式一:按照查詢嵌套處理 = 子查詢-->
<select id="getStudents" resultMap="resultStudents">
        select * from mybatis.student;
</select>
<resultMap id="resultStudents" type="student">
        <!--association:屬性是對象時使用-->
        <!--collection:屬性是集合時候用-->
     <association property="teacher" column="tid" javaType="teacher" select="getTeachers"/>
</resultMap>
<select id="getTeachers" resultType="teacher">
    select * from mybatis.teacher where id = #{tid}
</select>
  • 測試
@Test
    public void getStudents() {
        StudentMapper studentMapper = MyBatisUtil.getSqlSession().getMapper(StudentMapper.class);
        List<Student> students = studentMapper.getStudents();
        for (Student student : students) {
            System.out.println(student);
        }
        /*
            Student(id=1, name=小紅, teacher=Teacher(id=1, name=秦老師))
         */
}

10.2:聯表查詢=結果嵌套

  • 使用多表查詢,避免寫多個sql
  • mapper.xml

<!--方式二:按照結果嵌套處理 = 聯表查詢-->
<select id="getStudents1" resultMap="resultStudents1">
    select  s.id sid,s.name sname,t.name tname
    from student s,teacher t
    where s.tid = t.id;
</select>
<resultMap id="resultStudents1" type="student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <association property="teacher" javaType="teacher">
        <result property="name" column="tname"/>
    </association>
</resultMap>
  • 測試:與嵌套查詢結果沒變
@Test
    public void getStudents1() {
        StudentMapper studentMapper = MyBatisUtil.getSqlSession().getMapper(StudentMapper.class);
        List<Student> students = studentMapper.getStudents1();
        for (Student student : students) {
            System.out.println(student);
        }
        /*
            Student(id=1, name=小紅, teacher=Teacher(id=0, name=秦老師))
         */
    }

11 一對多

  • 配置環境:pojo和dao下的mapper接口
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private Integer id;
    private String name;
    private int tid;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private int id;
    private String name;
    //一對多的集合
    private List<Student> students;
}
public interface StudentMapper {
    
}

public interface TeacherMapper {
    /**
     * 子查詢:按查詢嵌套查詢
     * @param tid
     * @return
     */
    Teacher getTeacher(@Param("id") int tid);
    /**
     * 聯表查詢:按結果嵌套查詢
     * @param tid
     * @return
     */
    Teacher getTeacher1(@Param("id") int tid);
}

11.1: 子查詢

<!--子查詢:按查詢嵌套查詢-->
<select id="getTeacher" resultMap="teacherToStudent">
    select id, name from mybatis.teacher where id = #{id}
</select>
<resultMap id="teacherToStudent" type="teacher">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <!--  column="id"是teacher表中的id-->
    <collection property="students" javaType="List" ofType="student"
                column="id" select="getStudentsByTeacherId" />
</resultMap>
<select id="getStudentsByTeacherId" resultType="student">
    select * from mybatis.student where tid = #{id}
</select>

測試:

@Test
public void getTeacher() {
    TeacherMapper teacherMapper = MyBatisUtil.getSqlSession().getMapper(TeacherMapper.class);
    Teacher teacher = teacherMapper.getTeacher(1);
    System.out.println(teacher);
    /*
        若是id=0怎麼解決?就是collection中配置result property="id" column="id"
        Teacher(id=1, name=秦老師, students=[Student(id=1, name=小紅, tid=1),Student(id=2, name=小明, tid=1)...
         */
}

11.2: 聯表查詢

<!--聯表查詢:按結果嵌套查詢-->
<select id="getTeacher1" resultMap="teacherAndStudent">
    select  s.id sid,s.name sname,t.name tname,t.id tid
    from mybatis.teacher t,mybatis.student s
    where s.tid=t.id and t.id =#{id}
</select>
<resultMap id="teacherAndStudent" type="teacher">
    <result property="id" column="tid"/>
    <result property="name" column="tname"/>
    <!--集合對象用collection綁定,javaType是返回單個屬性,不能返回集合,
返回屬性是集合用ofType綁定-->
    <collection property="students" ofType="student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="tid" column="tid"/>
    </collection>
</resultMap>

測試:

@Test
public void getTeacher1() {
    TeacherMapper teacherMapper = MyBatisUtil.getSqlSession().getMapper(TeacherMapper.class);
    Teacher teacher = teacherMapper.getTeacher1(1);
    System.out.println(teacher);
    /*
        Teacher(id=1, name=秦老師, students=[Student(id=1, name=小紅, tid=1), Student(id=2, name=小明, tid=1)...
         */
}

11.3: 多表查詢小結

  • 多對一中的一:association
  • 一對多中的多:collection
  • javaType & ofType

    • javaType:指定實體類中的屬性的java返回值類型
    • ofType:映射List或某些指定的pojo泛型的類型,聯想List中的泛型類型Student用ofType綁定
  • 注意點:

    • 保證SQL的可讀性,建議使用聯表查詢

11.4: 面試題自學補充

  • MySQL引擎
  • InnoDB底層原理
  • 索引和索引優化

12 動態SQL

  • 概念:動態 SQL 是 MyBatis 的強大特性之一,簡化了原生複雜SQL書寫
  • 四個判斷條件:

    • if
    • choose (when, otherwise)
    • trim (where, set)
    • foreach
  • 搭建數據庫
CREATE TABLE blog(
    id VARCHAR(50) NOT NULL COMMENT '博客id',
    title VARCHAR(100) NOT NULL COMMENT '博客標題',
    author VARBINARY(30) NOT NULL COMMENT'博客做者',
    # 數據庫時間DateTime類型=pojo中的Date類型
    # 下劃線命名調到pojo中的駝峯式命令,須要mybatis開啓駝峯式命令
    create_time DATETIME NOT NULL COMMENT'建立時間',
    views INT(30) NOT NULL COMMENT'瀏覽量'
)ENGINE=INNODB DEFAULT CHARSET = utf8;
  • pojo和駝峯式命名
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Blog {
    private String id;
    private String title;
    private String author;
    /**
     * 下劃線命名調到pojo中的駝峯式命令,須要mybatis開啓駝峯式命令
     */
    private Date createTime;
    private int views;
}
<!--解決駝峯命令,使用setting配置,只能用戶數據庫中的xx_xx編程bean中的駝峯式-->
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
  • Utils
public class MyBatisUtil {
    /**
     * 提高sqlSessionFactory做用域,便於全局使用
     */
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            /*
            使用Mybatis第一步,獲取sqlSessionFactory對象
             */
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * sqlSessionFactory對象獲取SQLSession實例
     */
    public static SqlSession getSqlSession() {

        return sqlSessionFactory.openSession(true);
    }
}
//隨機產生數據庫表中的views
public class UUIDUtils {
    public static String getId() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    @Test
    public void getUUId() {
        System.out.println(UUIDUtils.getId());
    }
}
  • mapper接口
public interface BlogMapper {
    int addBlog(Blog blog);
    List<Blog> queryBlogByIf(Map map);
    List<Blog> queryBlogByWhere(Map map);
    List<Blog> queryBlogByForeach(Map map);
}

if

  • 概念:sql常見的場景就是判斷使用
  • 方式一:經過< if >直接使用,或者< include>跳轉sql使用時候,須要保證where成立,因此須要在sql語句中加上相似where 1= 1 或者 where state = 'active'等語句

    • 這裏使用了SQL片斷複用,見後面講解

<!--if:經過include跳轉到使用,缺點是必須寫上判斷條件成立 where 1=1-->
<select id="queryBlogByIf" parameterType="map" resultType="Blog">
    select * from mybatis.blog where 1=1
    <include  refid="if_title_author_like" />
</select>
<sql id="if_title_author_like">
    <if test="title !=null">
        and  title like #{title}
    </if>
    <if test="author !=null">
        and author like #{author}
    </if>
</sql>
  • 方式二:經過< where >和< if >混合使用,就不用手動加上where 1= 1
<!--if:經過where直接使用/直接使用if判斷,可是不推薦。原理:若是test存在,就自動加上where -->
<select id="queryBlogByWhere" parameterType="map" resultType="Blog">
    select * from mybatis.blog
    <where>
        <if test="id !=null">
            id like #{id}
        </if>
        <if test="views !=null">
            and views like #{views}
        </if>
    </where>
</select>
  • 測試:模糊查詢須要封裝通配符
@Test
public void queryBlogByIf() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
    Map<String, String> map = new HashMap<>();
    //模糊查詢,使用map的好處
    map.put("title", "%my%");
    List<Blog> blogs = blogMapper.queryBlogByIf(map);
    System.out.println(blogs);
    sqlSession.close();
}

@Test
public void queryBlogByWhere() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
    Map<String, Object> map = new HashMap<>();
    //map.put("id","%4%");
    map.put("views", "%2%");
    List<Blog> blogs = blogMapper.queryBlogByWhere(map);
    System.out.println(blogs);
    sqlSession.close();
}

choose

  • 概念:有時候,咱們不想使用全部的條件,而只是想從多個條件中選擇一個使用。針對這種狀況,MyBatis 提供了 choose 元素,它有點像 Java 中的 switch 語句。
  • 需求:傳入了 「title」 就按 「title」 查找,傳入了 「author」 就按 「author」 查找的情形。若二者都沒有傳入,就返回標記爲 featured 的 BLOG
  • 細節:choose只能知足其中一個whenotherwisw;使用WHERE state = ‘ACTIVE’等保證where成立
<!--官網案例-->
<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

where

  • 單獨使用< if > 的缺點:若是沒有查詢條件或者第一個條件沒有知足,就會出現錯誤的sql語句:
<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE
  <if test="state != null">
    state = #{state}
  </if>
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>
  • 出現錯誤sql:
# 沒有條件成立
SELECT * FROM BLOG
WHERE
# 第一個條件沒有成立
SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’
  • 使用< where >

    • 優點:where 元素只會在子元素返回任何內容的狀況下才插入 「WHERE」 子句。並且,若子句的開頭爲 「AND」 或 「OR」,where 元素也會將它們去除。
<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>
  • 使用自定義 trim 元素來定製 where 元素的功能

    • 與< where>等價的< trim>

      • prefix="WHERE"知足條件,自動添加的字段:prefixOverrides自動忽略的字段,細節:AND |OR 二者後面都包含了一個空格,這是正確sql的書寫要點
<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

set

  • 概念:用於動態update語句的叫作 set。set 元素能夠用於動態包含須要更新的列,忽略其它不更新的列

    • 等價的trim語句,set 元素會動態地在行首插入 SET 關鍵字,並會刪掉額外的逗號(這些逗號是在使用條件語句給列賦值時引入的)= 也就是說其實mybatis自動在update中set就給你加上了逗號,可是你本身手寫加上了,< set> 也會給你忽略掉
<trim prefix="SET" suffixOverrides=",">
  ...
</trim>
<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

foreach

  • 概念:動態 SQL 的另外一個常見使用場景是對集合進行遍歷(尤爲是在構建 IN 條件語句的時候)
  • 原生: SELECT * FROM blog WHERE 1=1 AND (id=1 OR id=2);
  • 細節:if < where> 多個條件成立時,就會忽略掉原生中and書寫

<select id="queryBlogByForeach" parameterType="map" resultType="Blog">
    select * from mybatis.blog
    <where>
           <foreach collection="ids" item="id" open="(" separator="or" close=")">
            id=#{id}
        </foreach>
    </where>
</select>
<!--官網案例:使用in時-->
<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

SQL片斷

  • 回顧:前面的< where >結合< if>,咱們將公共的SQL語句抽取出來,複用.
  • 使用:< sql id= > 標籤和< include refid= >引用
  • 細節:

    • 最好基於單表使用sql片斷,多表別的表不必定支持
    • 使用sql片斷複用,不要使用< where >標籤,由於它內置了會忽略掉某些字段
<sql id="if_title_author_like">
    <if test="title !=null">
        and  title like #{title}
    </if>
    <if test="author !=null">
        and author like #{author}
    </if>
</sql>

<select id="queryBlogByIf" parameterType="map" resultType="Blog">
    select * from mybatis.blog where 1=1
    <include refid="if_title_author_like" />
</select>

bind(瞭解)

  • bind 元素容許你在 OGNL 表達式之外建立一個變量,並將其綁定到當前的上下文。好比:
<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>

13 緩存

  • 咱們再次查詢相同數據時候,直接走緩存,就不用再存了,解決速度問題
  • 什麼樣的數據須要用到緩存?

    • 常常查詢而且不常常改變的數據,可使用緩存

緩存原理圖(重要)

二級緩存工做原理:

  • 一次sqlsession是一級緩存,查詢操做結束後,是默認保存在一級緩存中的
  • 若是開啓二級緩存,必須先關閉一級緩存,這時候的緩存數據會保存到二級緩存中
  • 第二次查詢時候,用戶操做會嫌去二級緩存中查找

一級緩存

  • 默認狀況下,只啓用了本地的會話(一級)緩存,它僅僅對一個會話中的數據進行緩存。

    • 把一級緩存想象成一個會話中的map,便於理解

  • 緩存失效

    • 增刪改會把全部的sql緩存失效,下次會重寫從數據庫中查
    • 查詢不一樣的東西,查詢不一樣的mapper.xml
    • 手動清除緩存: sqlSession.clearCache();

二級緩存

  • mybatis-config,xml開啓全局緩存
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <!--開啓全局緩存 默認是開啓的,顯示寫便於可讀-->
    <setting name="cacheEnabled" value="true"/>
</settings>
  • mappper.xml開啓二級緩存:

    <!--開啓二級緩存-->
    <cache/>
    • 映射語句文件中的全部 select 語句的結果將會被緩存。
    • 映射語句文件中的全部 insert、update 和 delete 語句會刷新緩存。
    • 緩存會使用最近最少使用算法(LRU, Least Recently Used)算法來清除不須要的緩存。
    • 緩存不會定時進行刷新(也就是說,沒有刷新間隔)。
    • 緩存會保存列表或對象(不管查詢方法返回哪一種)的 1024 個引用。
    • 緩存會被視爲讀/寫緩存,這意味着獲取到的對象並非共享的,能夠安全地被調用者修改,而不干擾其餘調用者或線程所作的潛在修改。

      • 讀寫緩存須要pojo開啓序列化操做
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private int id;
    private String name;
    private String pwd;

    public static void main(String[] args) {
        new ArrayList<>();
        new HashMap<>();
        new LinkedList<>();
    }
}
  • 開啓二級緩存時,能夠指定參數

    <cache
      eviction="FIFO"
      flushInterval="60000"
      size="512"
      readOnly="true"/>
    • eviction:清楚算法,默認LRU

      • LRU – 最近最少使用:移除最長時間不被使用的對象。
      • FIFO – 先進先出:按對象進入緩存的順序來移除它們。
      • SOFT – 軟引用:基於垃圾回收器狀態和軟引用規則移除對象。
      • WEAK – 弱引用:更積極地基於垃圾收集器狀態和弱引用規則移除對象。
    • flushInterval:刷新緩存間隔,默認無
    • size:最多緩存數量,默認1024
    • readOnly:只讀緩存;寫操做會不走緩存,直接從數據庫查詢,默認是讀/寫緩存
  • 使用二級緩存

    • 測試語句
public class MyTest {
    @Test
    public void queryUserById() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        SqlSession sqlSession1 = MyBatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.queryUserById(1);
        //關閉上次sqlSession,若是開啓二級緩存,就會把此次的一級緩存保存到二級緩存中
        sqlSession.close();
        System.out.println("=================");

        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
        //若是開啓二級緩存,下次相同mapper的查詢操做會先重二級緩存中查找
        User user1 = mapper1.queryUserById(1);
        sqlSession1.close();
    }
}

自定義緩存(瞭解)

  • 概念:ehcache是一個分佈式緩存,主要面向通用緩存
  • 手寫或者導入第三方的ehcache緩存依賴
  • 因此能夠自定義ehcache.xml配置文件
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.0</version>
</dependency>
  • 在mapper.xml中配置
<cache type="org.mybatis.caches.encache.EhcacheCache"/>
  • 在resource中建立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">
      
      <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"/>
      
      <!--
         diskStore:爲緩存路徑,ehcache分爲內存和磁盤兩級,此屬性定義磁盤的緩存位置。參數解釋以下:
         user.home – 用戶主目錄
         user.dir  – 用戶當前工做目錄
         java.io.tmpdir – 默認臨時文件路徑
       -->
      <diskStore path="java.io.tmpdir/Tmp_EhCache"/>
      <!--
         defaultCache:默認緩存策略,當ehcache找不到定義的緩存時,則使用這個緩存策略。只能定義一個。
       -->
      <!--
        name:緩存名稱。
        maxElementsInMemory:緩存最大數目
        maxElementsOnDisk:硬盤最大緩存個數。
        eternal:對象是否永久有效,一但設置了,timeout將不起做用。
        overflowToDisk:是否保存到磁盤,當系統當機時
        timeToIdleSeconds:設置對象在失效前的容許閒置時間(單位:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,默認值是0,也就是可閒置時間無窮大。
        timeToLiveSeconds:設置對象在失效前容許存活時間(單位:秒)。最大時間介於建立時間和失效時間之間。僅當eternal=false對象不是永久有效時使用,默認是0.,也就是對象存活時間無窮大。
        diskPersistent:是否緩存虛擬機重啓期數據 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
        diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每一個Cache都應該有本身的一個緩衝區。
        diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒。
        memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理內存。默認策略是LRU(最近最少使用)。你能夠設置爲FIFO(先進先出)或是LFU(較少使用)。
        clearOnFlush:內存數量最大時是否清除。
        memoryStoreEvictionPolicy:可選策略有:LRU(最近最少使用,默認策略)、FIFO(先進先出)、LFU(最少訪問次數)。
        FIFO,first in first out,這個是你們最熟的,先進先出。
        LFU, Less Frequently Used,就是上面例子中使用的策略,直白一點就是講一直以來最少被使用的。如上面所講,緩存的元素有一個hit屬性,hit值最小的將會被清出緩存。
        LRU,Least Recently Used,最近最少使用的,緩存的元素有一個時間戳,當緩存容量滿了,而又須要騰出地方來緩存新的元素的時候,那麼現有緩存元素中時間戳離當前時間最遠的元素將被清出緩存。
     -->
  </ehcache>

Redis

  • 自學,另外一個開始
相關文章
相關標籤/搜索