Mybatis是一个操作数据库的持久化框架

Mybatis的初次使用

1.数据库搭建

在mysql中创建这样的数据表
image.png

2.实体类实现

在java中创建与数据表字段相对应的对象

  1. public class Customer {
  2. private Integer id;
  3. private String name;
  4. private String jobs;
  5. private String phone;
  6. public Integer getId() {
  7. return id;
  8. }
  9. public void setId(Integer id) {
  10. this.id = id;
  11. }
  12. public String getName() {
  13. return name;
  14. }
  15. public void setName(String name) {
  16. this.name = name;
  17. }
  18. public String getJobs() {
  19. return jobs;
  20. }
  21. public void setJobs(String jobs) {
  22. this.jobs = jobs;
  23. }
  24. public String getPhone() {
  25. return phone;
  26. }
  27. public void setPhone(String phone) {
  28. this.phone = phone;
  29. }
  30. @Override
  31. public String toString() {
  32. return "Customer{" +
  33. "id=" + id +
  34. ", name='" + name + '\'' +
  35. ", jobs='" + jobs + '\'' +
  36. ", phone='" + phone + '\'' +
  37. '}';
  38. }
  39. }

接口类的创建

  1. public interface CustomerDao {
  2. public Customer getCustomerById(int id);
  3. }

3.mybatis的全局配置文件

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE configuration
  3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <!--配置-->
  6. <configuration>
  7. <!--配置环境-->
  8. <environments default="mysql">
  9. <!--环境变量-->
  10. <environment id="mysql">
  11. <!--事务管理器-->
  12. <transactionManager type="JDBC"/>
  13. <!--数据源 配置链接池-->
  14. <dataSource type="pooled">
  15. <property name="driver" value="com.mysql.jdbc.Driver"/>
  16. <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
  17. <property name="username" value="root"/>
  18. <property name="password" value="123456"/>
  19. </dataSource>
  20. </environment>
  21. </environments>
  22. <!--引入每个接口的实现器-->
  23. <mappers>
  24. <!--resource:从类路径寻找-->
  25. <mapper resource="test/CustomerMapper.xml"></mapper>
  26. </mappers>
  27. </configuration>

4.实现操作数据库语句的Sql映射文件

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <!--命名空间 :写接口的全类名,告诉mybatis这个文件实现哪个接口的-->
  6. <mapper namespace="com.mybatis.test.dao.CustomerDao">
  7. <!--
  8. select:数据库相关操作
  9. id:操作数据库的方法的名字
  10. resultType:指定方法操作后的返回值类型
  11. -->
  12. <select id="getCustomerById" resultType="com.mybatis.test.bean.Customer">
  13. select * from Customer where id = #{id}
  14. </select>
  15. </mapper>

5.log4j的配置

log4j的配置文件,能够在控制台出看到相关的数据库操作信息(可写可不写)

  1. log4j.rootLogger=DEBUG,Console
  2. log4j.appender.Console=org.apache.log4j.ConsoleAppender
  3. log4j.appender.Console.layout=org.apache.log4j.PatternLayout
  4. log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
  5. log4j.logger.org.apache=INFO

6.代码测试

单元测试类

  1. @Test
  2. public void test01() throws IOException {
  3. String source="mybatis-config.xml";
  4. InputStream inputStream = Resources.getResourceAsStream(source);
  5. //SqlSessionFactory:是工厂,负责创建sqlSession对象
  6. //SqlSession sql会话(代表和数据库的一次会话)
  7. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  8. //获得和数据库的一次会话,getConnection()
  9. SqlSession sqlSession = sqlSessionFactory.openSession();
  10. CustomerDao mapper = sqlSession.getMapper(CustomerDao.class);
  11. Customer customerById = mapper.getCustomerById(1);
  12. System.out.println(customerById);
  13. }

实验结果
image.png

执行过程

1.获取mybatis的全局配置文件

  1. String source="mybatis-config.xml";

2.将配置资源文件转化为流

  1. InputStream inputStream = Resources.getResourceAsStream(source);

3.通过SqlSessionFactoryBuilder的类中的build方法来创建SqlSession工厂

  1. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

4.sqlsession工厂通过openSession获得和数据库的一次会话

  1. SqlSession sqlSession = sqlSessionFactory.openSession();

5.通过接口获得mapper,它是customer对象

  1. CustomerDao mapper = sqlSession.getMapper(CustomerDao.class);

6.调用接口的数据库操作函数,获得数据库中的记录

  1. Customer customerById = mapper.getCustomerById(1);

实现方法二

相比较上一个,实现实体类的接口,通过映射器接口调用,获得相应的数据库操作方法还获取数据库中的字段转化为一个对象。

  1. CustomerDao mapper = sqlSession.getMapper(CustomerDao.class);
  2. Customer customerById = mapper.getCustomerById(1);

该命名就可以直接映射到在命名空间中同名的映射器类,并将已映射的 select 语句匹配到对应名称、参数和返回类型的方法。第一种方法有很多优势,首先它不依赖于字符串字面值,会更安全一点。

第二种实现方法

实现映射配置文件

  1. ?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="com.mybatis.test.mapper.CustomerMapper">
  6. <select id="getCustomerById" parameterType="Integer" resultType="com.mybatis.test.bean.Customer">
  7. select * from Customer where id = #{id}
  8. </select>
  9. </mapper>

命名空间设置为包名加映射文件名,此配置方法在src/java中不会自动的装配到.class文件中,所以配置之后如果不做声明,这无法运行。
在Maven中说明,要编码的配置文件

  1. <build>
  2. <resources>
  3. <resource>
  4. <directory>src/main/java</directory>
  5. <includes>
  6. <include>**/*.xml</include>
  7. </includes>
  8. </resource>
  9. </resources>
  10. </build>

只有添加了这样的信息,才能编码进字节码文件中。
在全局配置中的mappers加入此语句,来获得映射文件

  1. <mapper resource="com/mybatis/test/mapper/CustomerMapper.xml"></mapper>

因此我们只需要在单元测试中调用这样的函数就行。

  1. Customer customer = sqlSession.selectOne("com.mybatis.test.mapper.CustomerMapper.getCustomerById", 2);

通过这样,也可以实现sql语句的查询。

MyBatis实现增删查改

  1. <!--增删改不用放回类型-->
  2. <update id="updateCustomer">
  3. update Customer set username=#{username},jobs=#{jobs},phone=#{phone} where id=#{id}
  4. </update>
  5. <delete id="deleteCustomer">
  6. delete from Customer where id=#{id}
  7. </delete>
  8. <insert id="insertCustomer">
  9. insert into Customer(username,jobs,phone)
  10. values(#{username},#{jobs},#{phone})
  11. </insert>
  12. <select id="selectCustomer" resultType="com.mybatis.test.bean.Customer">
  13. select * from Customer
  14. </select>

在资源文件夹下的映射文件中进行改动。
将sqlsession工厂写在工具类中

  1. public class MybatisUtils {
  2. private static SqlSessionFactory sqlSessionFactory;
  3. static{
  4. String source="mybatis-config.xml";
  5. InputStream inputStream = null;
  6. try {
  7. inputStream = Resources.getResourceAsStream(source);
  8. } catch (IOException e) {
  9. e.printStackTrace();
  10. }
  11. sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  12. }
  13. public static SqlSession getSqlSession(){
  14. return sqlSessionFactory.openSession();
  15. }
  16. }

进行单元测试

  1. @Test
  2. public void test02(){
  3. SqlSession sqlSession = MybatisUtils.getSqlSession();
  4. CustomerDao customerDao = sqlSession.getMapper(CustomerDao.class);
  5. //修改
  6. Customer customer = new Customer(1,"jack","teacher","88888888");
  7. Customer customer1 = new Customer("tomcat","server","12345678911");
  8. customerDao.updateCustomer(customer);
  9. customerDao.insertCustomer(customer1);
  10. customerDao.deleteCustomer(4);
  11. //查询所有的记录
  12. List<Customer> customers = customerDao.selectCustomer();
  13. for (Customer customer2 : customers) {
  14. System.out.println(customer2);
  15. }
  16. sqlSession.commit();
  17. sqlSession.close();
  18. }

原来数据库中有的表
image.png
运行后的表
image.png

MyBatis全局配置文件

image.png
注:mybatis标签的书写顺序要严格按照以上要求进行书写,不然会报错
全局文件:指导mybatis正确运行的一些设置
映射文件:是对Mapper接口的实现的描述

  1. sqlSession.getMapper(Custome.class).getClass();

获得的对象是一个代理对象

properties标签

主要用来引入外部配置文件信息,和spring相同,可以用于数据源信息的配置
外部配置文件

  1. username=root
  2. password=348102
  3. jdbc.url=jdbc:mysql://localhost:3306/mybatis
  4. driverclass=com.mysql.jdbc.Driver

全局配置文件

  1. <!--和spring的context:property-placeholder一样,用于引入外部配置文件
  2. 标签属性:resource:从类路径中选择
  3. url:引用磁盘路径或者网络路径的资源
  4. -->
  5. <properties resource="dbconfig.properties"></properties>
  6. <!--配置环境-->
  7. <environments default="mysql">
  8. <!--环境变量-->
  9. <environment id="mysql">
  10. <!--事务管理器-->
  11. <transactionManager type="JDBC"/>
  12. <!--数据源 配置链接池-->
  13. <dataSource type="pooled">
  14. <!--${}取出外部配置文件中的值-->
  15. <property name="driver" value="${driverclass}"/>
  16. <property name="url" value="${jdbcurl}"/>
  17. <property name="username" value="${username}"/>
  18. <property name="password" value="${password}"/>
  19. </dataSource>
  20. </environment>
  21. </environments>

settings标签

可以设置mybatis中开启相应的功能,比如二级缓冲,和驼峰命名等相关设置

  1. <settings>
  2. <!--开启驼峰命名规则 假设实体类中userName可以对应数据库user_name这样的字段-->
  3. <setting name="mapUnderscoreToCamelCase" value="true"/>
  4. </settings>

当相应带有_符号的字段取不到值时,可以开启驼峰命名规则进行获取。

typeAliases标签

对相应的bean对象起别名,减去全类名书写时繁琐
xml配置

  1. <!--起别名-->
  2. <typeAliases>
  3. <!--不指定alias名字默认类名作为别名,不区分大小写-->
  4. <typeAlias type="com.mybatis.test.bean.Customer" alias="customer"></typeAlias>
  5. <!--对包下的使用bean起别名-->
  6. <package name="com.mybatis.test.bean"/>
  7. </typeAliases>

注解配置

  1. @Alias("custom")

映射文件

  1. <select id="getCustomerById" resultType="Custom">
  2. select * from Customer where id = #{id}
  3. </select>

注:推荐使用全类名

typeHandlers标签

MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。
类型处理器底层还是调用jdbc的setXX类型方法进行赋值的。
image.png
可自定义类型处理器

plugins标签

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed):执行器
  • ParameterHandler (getParameterObject, setParameters):参数处理器
  • ResultSetHandler (handleResultSets, handleOutputParameters):结果集处理器
  • StatementHandler (prepare, parameterize, batch, update, query):预处理器

比如分页插件,将mybatis查询出来的结果集进行分页。

environments标签

  1. <!--default:默认是使用哪个环境,使用id为mysql的环境-->
  2. <environments default="mysql">
  3. <!--环境变量-->
  4. <environment id="mysql">
  5. <!--事务管理器-->
  6. <transactionManager type="JDBC"/>
  7. <!--数据源 配置链接池-->
  8. <dataSource type="pooled">
  9. <!--${}取出外部配置文件中的值-->
  10. <property name="driver" value="${driverclass}"/>
  11. <property name="url" value="${jdbcurl}"/>
  12. <property name="username" value="${username}"/>
  13. <property name="password" value="${password}"/>
  14. </dataSource>
  15. </environment>
  16. <environment id="oracle">
  17. <!--事务管理器-->
  18. <transactionManager type="JDBC"/>
  19. <!--数据源 配置链接池-->
  20. <dataSource type="pooled">
  21. <!--${}取出外部配置文件中的值-->
  22. <property name="driver" value="${driverclass}"/>
  23. <property name="url" value="${jdbcurl}"/>
  24. <property name="username" value="${username}"/>
  25. <property name="password" value="${password}"/>
  26. </dataSource>
  27. </environment>
  28. </environments>

注:虽然mybatis中有数据源和事务管理的功能,但是在和spring整合之后,就交给spring进行控制了。

databaseIdProvider标签

做数据库移植使用,实际开发中基本不会使用,数据库一般都是决定好的。

  1. <!--配置数据库产商信息-->
  2. <databaseIdProvider type="DB_VENDOR">
  3. <!--在映射文件中的sql语句添加上databaseId的值,指定他在那种数据库中运行-->
  4. <property name="SQL Server" value="sqlserver"/>
  5. <property name="MySQL" value="mysql"/>
  6. <property name="Oracle" value="oracle" />
  7. </databaseIdProvider>

mappers标签

  1. <!--引入每个接口的实现器-->
  2. <mappers>
  3. <!--
  4. class:从接口中寻找,注意,相应的映射文件得和接口在同一包下
  5. url:从磁盘或者网络路径中寻找
  6. resource:从类路径寻找-->
  7. <mapper resource="test/CustomerMapper.xml"></mapper>
  8. <mapper resource="com/mybatis/test/mapper/CustomerMapper.xml"></mapper>
  9. </mappers>

批量注入

  1. <mappers>
  2. <package name="com.mybatis.test.dao"/>
  3. </mappers>

可以配合逆向工程生成的接口和映射文件相匹配,注意两者要在同一个包下。

SQL映射文件

MyBatis中对于数据库的操作相较jdbc简单了许多,下面是xml映射文件中所用的标签。
image.png

select标签

select标签中的属性
image.png

insert, update 和 delete标签

标签中的元素
image.png

获取自增主键的值

有些数据库的主键是可以自增的,比如mysql和Sql Server,因此当我们为数据添加值时,并不一定需要设置自增主键的值。

  1. <!--在插入是获取自增主键的值
  2. useGeneratedKeys:会调用jdbc中的方法,自动获取自增主键的值
  3. keyProperty:将自增主键赋值给对象中的那个属性
  4. -->
  5. <insert id="insertCustomer" useGeneratedKeys="true" keyProperty="id">
  6. insert into Customer(username,jobs,phone)
  7. values(#{username},#{jobs},#{phone})
  8. </insert>

单元测试

  1. Customer customer = new Customer("java","teacher","88888888");
  2. mapper.insertCustomer(customer);
  3. System.out.println(customer.getId());

结果
image.png
可以看到,打印出插入时的自增id值了。

获取非自增主键的值

oracle的主键是不自增的

  1. <!--查找非自增主键的值-->
  2. <insert id="insertCustomer2">
  3. <!--在插入语句运行前,从数据库中找到最大的主键值+1,赋值给对象的id,和select标签相似,需设定返回类型-->
  4. <selectKey order="BEFORE" keyProperty="id" resultType="Integer">
  5. select max(id)+1 from Customer;
  6. </selectKey>
  7. insert into Customer(id,username,jobs,phone)
  8. values(#{id},#{username},#{jobs},#{phone})
  9. </insert>

单元测试

  1. //非自增
  2. Customer customer1 = new Customer(null,"java","teacher","88888888");
  3. mapper.insertCustomer(customer1);
  4. System.out.println(customer1.getId());

结果
image.png
我们获取到了非自增主键的值,并且成功插入了。

传入参数的取值

以select为例

  • 传入一个参数,${参数名}中参数名可以随便写
  • 传入多个参数时,${参数名}得使用0,1…n或者param1,param2…paramN

在CustomeDao类中添加这样的查找方法

  1. Customer getCustomerByIdAndName(int id,String username);

映射文件配置如下

  1. <select id="getCustomerByIdAndName" resultType="com.mybatis.test.bean.Customer">
  2. select * from Customer where id = #{param1} and username = #{param2};
  3. </select>

才可以获取到相应的Custome记录。
如果想指定使用参数名,则必须告诉mybatis,接口对象方法参数中声明。

  1. Customer getCustomerByIdAndName(@Param("id") int id, @Param("username") String username);

用Param注解去声明我们使用的参数名。
原因:

  • 在mybatis中,多个参数传入时,会自动将参数封装为一个map,map中使用的key一般是0~n或者param1~n,去匹配我们传入的值。

传入pojo

  • 取值#{pojo.属性}

传入map

  • 将传入的map直接使用,跟传入多个参数相同

    ${}和#{}的区别

    1. <select id="getCustomerByIdAndName" resultType="com.mybatis.test.bean.Customer">
    2. select * from Customer where id = ${id} and username = #{username};
    3. </select>

    结果:
    image.png
    #{}:参数预编译的方式,参数位置用?代替,参数是后来预编译进去的,不会有sql注入问题
    ${}:没有进行预编译,直接和sql进行拼串,不安全

    返回list

    Mapper接口

    1. List<Customer> selectCustomer();

    映射文件

    1. <select id="selectCustomer" resultType="com.mybatis.test.bean.Customer">
    2. select * from Customer
    3. </select>

    mybatis会将查找出来的字段,根据类的变量名赋值,之后自动封装成一个list。

    返回map

    1. Map<Object,Object> getCustomerByIdReturnMap(int id);
    1. <select id="getCustomerByIdReturnMap" resultType="java.util.Map">
    2. select * from Customer where id = #{id}
    3. </select>

    image.png
    MyBatis会自动将数据库中的字段分装为key和value。

    返回多个Map

    在映射类中指定相应的MapKey的值,将指定数据库中的那个字段作为key

    1. @MapKey("id")
    2. Map<Integer,Customer> getCutomerRetrunMap();
    1. <select id="getCutomerRetrunMap" resultType="java.util.Map">
    2. select * from Customer
    3. </select>

    获得结果
    image.png
    获得的结果是一个HashMap,不是一个Customer对象。
    需要在映射文件中指定。

    1. <select id="getCutomerRetrunMap" resultType="com.mybatis.test.bean.Customer">
    2. select * from Customer
    3. </select>

    才能获得customer对象。

    resultMap自定义封装字段名

    当数据库中的字段是不满足驼峰命名规则时,需要手动的自定数据库中字段对应bean中的属性,数据才能自动被封装到字段的类中。
    数据库中存在这样的一张表
    image.png
    定义的bean是这样的
    image.png
    可以看到,属性名和字段中的列名不相同,这样是无法将字段中的数据封装。
    image.png
    可以看到name和age得不到值。
    映射文件

    1. <select id="findCatById" resultType="com.mybatis.test.bean.Cat">
    2. select id id,cName name,cAge age from cat where id=#{id}
    3. </select>

    也可以通过为字段起别名来封装
    还有另一种方式,使用ResultMap来自定义封装

    1. <select id="findCatById" resultType="com.mybatis.test.bean.Cat" resultMap="myCat">
    2. select * from cat where id=#{id}
    3. </select>
    4. <!--
    5. id:指定唯一标识
    6. type:为哪个javabean自定义
    7. -->
    8. <resultMap id="myCat" type="com.mybatis.test.bean.Cat">
    9. <!--
    10. id标签,主键设置
    11. column:哪个主键列
    12. property:设置和主键列对应的bean中属性
    13. -->
    14. <id column="id" property="id"></id>
    15. <!--普通列的设置-->
    16. <result column="cName" property="name"></result>
    17. <result column="cAge" property="age"></result>
    18. </resultMap>

    这样指定记录中对应列,可以进行封装
    image.png

    级联查询

    一对一关系

    创建一个锁表
    image.png
    创建一个key表,并且用外键lockid将他们关联起来
    image.png
    实体类
    image.png
    查出key的同时将锁的信息查出
    映射文件配置

    1. <select id="findKeyById" resultMap="myKey">
    2. select k.id keyid,k.keyname keyname,l.id lockid,l.lockname lockname from `key` k, `lock` l where k.lockid=l.id and k.id=#{id}
    3. </select>
    4. <resultMap id="myKey" type="com.mybatis.test.bean.Key">
    5. <id column="keyid" property="id" ></id>
    6. <result column="keyname" property="keyName"></result>
    7. <result column="lockid" property="lock.id"></result>
    8. <result column="lockname" property="lock.lockName"></result>
    9. </resultMap>

    可以获得结果
    image.png
    使用association自定义封装级联对象

    1. <select id="findKeyById" resultMap="myKey">
    2. select k.id keyid,k.keyname keyname,l.id lockid,l.lockname lockname from `key` k, `lock` l where k.lockid=l.id and k.id=#{id}
    3. </select>
    4. <resultMap id="myKey" type="com.mybatis.test.bean.Key">
    5. <id column="keyid" property="id" ></id>
    6. <result column="keyname" property="keyName"></result>
    7. <!--<result column="lockid" property="lock.id"></result>-->
    8. <!--<result column="lockname" property="lock.lockName"></result>-->
    9. <!--
    10. javaType:自定分装对象的类型
    11. -->
    12. <association property="lock" javaType="com.mybatis.test.bean.Lock" >
    13. <id property="id" column="lockid"></id>
    14. <result column="lockname" property="lockName"></result>
    15. </association>
    16. </resultMap>

    多对一关系

    多对一关系,外键要放在多的一方,放在一的一方会导致列的不确定性。
    多个钥匙
    image.png
    一个房间锁
    image.png
    多对一个关系
    注:从多的一方看,每个记录都有对应一个记录匹配,是一对一关系;
    从一的一方看,每个记录有多个记录匹配,是多对一关系。
    因此,我们需要在锁实体类中创建一个集合,接受钥匙的信息。
    image.png
    映射文件配置

    1. <select id="findLockById" resultMap="myLock">
    2. select l.id lockid,l.lockname lockname, k.*
    3. from `key` k, `lock` l
    4. where k.lockid=l.id and l.id =#{id}
    5. </select>
    6. <resultMap id="myLock" type="com.mybatis.test.bean.Lock">
    7. <id property="id" column="lockid"></id>
    8. <result column="lockname" property="lockName"></result>
    9. <!--
    10. property:指定对象中的哪个集合
    11. ofType:指定集合中封装的类型
    12. -->
    13. <collection property="keys" ofType="com.mybatis.test.bean.Key">
    14. <id column="id" property="id"></id>
    15. <result column="keyname" property="keyName"></result>
    16. </collection>
    17. </resultMap>

    image.png
    可以看到,我们获得了三条记录,并且封装到Lock中。

    多对多关系

    无论是从哪一方看,都是多对一的关系,则在哪一方的表中增加外键都会导致表中列的不确定性,使用单独增加一个表,把两者的关系在表中存储。
    如果是多个学生和多个老师进行关联,则采用中间表进行查询,需要在这两个实体类中分别加入两者的集合,三表进行链接查询即可,并赋值。

    select分步查询

    不采用链接查询的方式,查出key的同时,并且查出lock的值。
    在lock映射文件中配置

    1. <select id="findSimpleLockById" resultType="com.mybatis.test.bean.Lock">
    2. select * from `lock` where id=#{id}
    3. </select>

    在key映射文件中配置

    1. <!--分布查询-->
    2. <select id="findSimpleKeyById" resultMap="myKey02">
    3. select * from `key` where id=#{id}
    4. </select>
    5. <resultMap id="myKey02" type="com.mybatis.test.bean.Key">
    6. <id column="id" property="id"></id>
    7. <result column="keyname" property="keyName"></result>
    8. <!--
    9. column:指定查出的哪一列出入select中作为查询值
    10. -->
    11. <association property="lock" select="com.mybatis.test.dao.LockMapper.findSimpleLockById" column="lockid" />
    12. </resultMap>

    select指定命名空间中的查询语句id,传入参数进行查询
    image.png
    可以看得到,sql语句有两条,分别查询了不同的表。

    按需加载

    分布查询会将所有的记录都查询出来,当我们不需要时,它会严重的影响性能,因此需要对查询时间进行设置,当我们需要时,才从数据库中查询出来。
    image.png
    在全局配置文件中设置

    1. <settings>
    2. <!--延迟加载-->
    3. <setting name="lazyLoadingEnabled" value="true"/>
    4. <!--属性按需加载-->
    5. <setting name="aggressiveLazyLoading" value="flase"/>
    6. </settings>

    则当我们去获得key中非lock对象的属性时,不会加载lock对象,就不会有sql语句出现。
    image.png
    可以看到,我们获取key中的钥匙名称时,并没有查找lock的sql语句出现。
    如果想取消全局配置文件中的lazy加载,则在映射文件中相应语句中设置

    1. <association property="lock" select="com.mybatis.test.dao.LockMapper.findSimpleLockById"
    2. column="lockid" fetchType="eager"/>

    由于是两次查询,所有实际上在工作中,为了减少数据库的访问次数,因此不怎么采用分布查询。

    动态SQL

    if标签

    使用if标签,但标签可以添加满足条件的查询条件
    在Mapper接口中添加这样的查询条件

    1. List<Customer> findCutomer(Customer customer);

    mybatis会自动将对象中的属性和值封装成一个map。
    在映射文件中配置

    1. <!-- 动态sql -->
    2. <select id="findCutomer" resultType="com.mybatis.test.bean.Customer">
    3. select * from Customer where
    4. <if test="id!=null and id>0">
    5. id > #{id} and
    6. </if>
    7. <if test="username!=null and username!=&quot;&quot;">
    8. username like #{username}
    9. </if>
    10. </select>

    单元测试

    1. //动态sql
    2. @Test
    3. public void test09(){
    4. SqlSession sqlSession = MybatisUtils.getSqlSession();
    5. CustomerDao mapper = sqlSession.getMapper(CustomerDao.class);
    6. Customer customer = new Customer();
    7. customer.setId(2);
    8. customer.setUsername("%t%");
    9. List<Customer> customer1 = mapper.findCutomer(customer);
    10. for (Customer customer2 : customer1) {
    11. System.out.println(customer2);
    12. }
    13. }

    结果
    image.png
    我们查到的记录自动分装到bean中。

    where标签

    where标签能自动去除前缀and,但是无法去除后缀的and
    配置文件

    1. <select id="findCutomer" resultType="com.mybatis.test.bean.Customer">
    2. select * from Customer
    3. <where>
    4. <if test="id!=null and id>0">
    5. id > #{id}
    6. </if>
    7. <if test="username!=null and username!=&quot;&quot;">
    8. and username like #{username}
    9. </if>
    10. </where>
    11. </select>

    上述配置,当第一条件不满足,第二条件满足时,where会紧跟这and,但是where标签会去除这个and
    单元测试

    1. //动态sql
    2. @Test
    3. public void test09(){
    4. SqlSession sqlSession = MybatisUtils.getSqlSession();
    5. CustomerDao mapper = sqlSession.getMapper(CustomerDao.class);
    6. Customer customer = new Customer();
    7. //customer.setId(2);
    8. customer.setUsername("%t%");
    9. List<Customer> customer1 = mapper.findCutomer(customer);
    10. for (Customer customer2 : customer1) {
    11. System.out.println(customer2);
    12. }
    13. }

    结果
    image.png

    trim标签

    截取字符串,和sql语句配合使用。可以去除/添加前缀字符串或者后缀字符串

    1. <select id="findCutomer" resultType="com.mybatis.test.bean.Customer">
    2. select * from Customer
    3. <trim prefix="where" suffixOverrides="and">
    4. <if test="id!=null and id>0">
    5. id > #{id} and
    6. </if>
    7. <if test="username!=null and username!=&quot;&quot;">
    8. username like #{username}
    9. </if>
    10. </trim>
    11. </select>

    image.png
    当满足第一个条件时,trim自动去除后缀and,可以看到查出所有满足条件的记录

    foreach标签

    遍历元素,假设传入参数是一个集合或者map

    1. List<Customer> findCutomerIn(@Param("idx") List<Integer> idx);

    映射文件配置

    1. <!--
    2. collection:指定集合类,默认是list的
    3. separator:添加的操作,两次遍历的集合
    4. open:以什么开始
    5. close:以什么结束
    6. item:集合中元素的值。map则对应是value
    7. index:集合的下标,如果是map,就是key
    8. -->
    9. <select id="findCutomerIn" resultType="com.mybatis.test.bean.Customer">
    10. select * from Customer where id in
    11. <foreach collection="idx" separator="," open="(" close=")" item="id">
    12. #{id}
    13. </foreach>
    14. </select>

    结果
    image.png

    choose标签

    选择分支标签,满足条件则随机选择一个

    1. <!--choose选择 -->
    2. <select id="findCutomerByChoose" resultType="com.mybatis.test.bean.Customer">
    3. select * from Customer
    4. <where>
    5. <choose>
    6. <when test="id!=null and id!=&quot;&quot;">
    7. id=#{id}
    8. </when>
    9. <when test="username!=null and username!=&quot;&quot;">
    10. username=#{username}
    11. </when>
    12. <otherwise>
    13. 1=1
    14. </otherwise>
    15. </choose>
    16. </where>
    17. </select>

    单元测试

    1. //choose分支结构
    2. @Test
    3. public void test11(){
    4. SqlSession sqlSession = MybatisUtils.getSqlSession();
    5. CustomerDao mapper = sqlSession.getMapper(CustomerDao.class);
    6. Customer customer = new Customer();
    7. customer.setId(2);
    8. customer.setUsername("Tom");
    9. List<Customer> cutomerByChoose = mapper.findCutomerByChoose(customer);
    10. System.out.println(cutomerByChoose);
    11. }

    image.png

    set标签

    自动的删除后缀逗号(,)

    1. <update id="updateCustomerBySet" >
    2. update Customer
    3. <set>
    4. <if test="username!=null and username!=&quot;&quot;">
    5. username=#{username},
    6. </if>
    7. <if test="jobs!=null and jobs!=&quot;&quot;">
    8. jobs=#{jobs},
    9. </if>
    10. <if test="phone!=null and phone!=&quot;&quot;">
    11. phone=#{phone}
    12. </if>
    13. </set>
    14. <where>
    15. id=#{id}
    16. </where>
    17. </update>

    结果
    image.png
    原来的tom变成了tom123

    OGNL表达式

    image.png

    bind标签

    绑定字符串等,在模糊查询中使用

    1. <!-- 动态sql -->
    2. <select id="findCutomer" resultType="com.mybatis.test.bean.Customer">
    3. select * from Customer
    4. <bind name="username2" value="'%'+username+'%'"/>
    5. <trim prefix="where" suffixOverrides="and">
    6. <if test="id!=null and id>0">
    7. id > #{id} and
    8. </if>
    9. <if test="username!=null and username!=&quot;&quot;">
    10. username like #{username2}
    11. </if>
    12. </trim>
    13. </select>

    bind标签将传入的username前后绑定了%,形成了一个新的值,适用多种数据库。

    sql标签

    可重用sql

    1. <sql id="mySql">select * from Customer</sql>
    2. <select id="getCustomerById" resultType="Custom">
    3. <include refid="mySql"></include> where id = #{id}
    4. </select>

    缓存

    缓存机制,为了加快数据库中的查询效率,缓存本身就是一个map

    一级缓存

    本地缓存,线程级别的缓存,sqlSession级别的缓存,只有在同一个sqlsession下才可以使用
    单元测试

    1. //一级缓存
    2. @Test
    3. public void test13(){
    4. SqlSession sqlSession = MybatisUtils.getSqlSession();
    5. CustomerDao mapper = sqlSession.getMapper(CustomerDao.class);
    6. Customer customerById = mapper.getCustomerById(1);
    7. System.out.println(customerById);
    8. Customer customerById1 = mapper.getCustomerById(1);
    9. System.out.println(customerById1);
    10. System.out.println(customerById==customerById1);
    11. }

    查询同一条记录,可以发现sql语句只发送一次,获得的两个对象都是同一个对象。
    image.png

    一级缓存失效

    失效情况

  • sqlsession关闭

  • 查询参数不同
  • 增删改操作后,mysql中记录被刷新也会再次寻找缓存
  • 缓存被清空

mybatis查询记录会先从一级缓存中查询,如果没有再去mysql中查询。

二级缓存

namespace级别的缓存,只对同一个映射文件有效,不同映射文件,有不同的二级缓存。
只有当sqlsession提交或者关闭时,记录才会被添加到二级缓存中。
mybatis默认不使用,需要配置。
使用步骤
1、全局配置文件中打开缓存

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

2、映射文件开启缓存

  1. <cache></cache>

3、bean类实现序列化接口

  1. public class Customer implements Serializable

单元测试

  1. //二级级缓存
  2. @Test
  3. public void test14(){
  4. SqlSession sqlSession = MybatisUtils.getSqlSession();
  5. CustomerDao mapper = sqlSession.getMapper(CustomerDao.class);
  6. Customer customerById = mapper.getCustomerById(1);
  7. System.out.println(customerById);
  8. sqlSession.close();
  9. SqlSession sqlSession1 = MybatisUtils.getSqlSession();
  10. CustomerDao mapper1 = sqlSession1.getMapper(CustomerDao.class);
  11. Customer customerById1 = mapper1.getCustomerById(1);
  12. System.out.println(customerById1);
  13. }

结果
image.png
可以看到第一个sqlsession关闭后,第二个sqlsession能从二级缓存中获得以前查询的记录

缓存的访问顺序

优先从二级缓存中寻找,如果没找到,再去一级缓存中拿,再没有,则从数据库中重新查找,放到一级缓存中,sqlsession关闭之后,纪录被保存到二级缓存。
因此,一二级缓存中不存在相同的纪录。

缓存原理

image.png

缓存标签

指定标签是否使用二级缓存

  1. useCache="true"

是否刷新一二级缓存

  1. flushCache="false"

增删改默认刷新缓存,查找默认不刷新缓存

sqlseesion的清空缓存只是对一级缓存有效。

执行器

simple执行器:

  • 每次执行都会sql语句的结果,但结果不会缓存

reuse执行器

  • 当执行一条sql语句会缓存,再次查询相同sql,则会在缓存中返回

batch执行器

  • 将所有的sql添加到批处理中,统一执行。更新大量数据时有明显的速度提升,如果是查询则跟simpleExecutor没什么区别。
  • 批处理操作必须经过手动提交事务

    缓存高级

    一级缓存本质是由三类执行器的父类(BaseExecutor)创建的,使用的是线程级别的缓存
    1. localcache
    二级缓存本质是通过装饰者模式,即ExchangingExecutor中存在一个指向BaseExecutor的示例,是属于会话级别的缓存。