MyBatis看这一篇就够了

MyBatis看这一篇就够了

​ MyBatis是一个优秀的基于Java的持久层框架,它内部封装了JDBC;试用MyBatis框架开发者只需要关注SQL语句本身。而不需要花费大量精力去处理:加载驱动、创建连接、创建Statement等繁杂过程。

​ MyBatis通过xml或者注解的方式将要执行的各种Statement配置起来,并通过Java对象和Statement中SQL的动态参数进行映射生成最终执行的SQL语句,最后由MyBatis框架执行SQL并将结果映射为Java对象返回。

MyBatis和传统JDBC比起来,优势在哪里?

  1. 数据库创建链接、释放资源造成系统浪费从而影响系统性能;虽然使用连接池也可以解决,但是MyBatis底层帮我们封装好了,简化了操作。
  2. SQL和Java代码耦合在一起,不利于代码的维护,在开发中Java代码变化的可能性不是很大,但是SQL会因为需求经常变化。MyBatis将SQL从代码中剥离,利于项目的维护。
  3. 使用PreparedStatement使用占位符的形式不够灵活,经常要加很多判断语句。导致代码可读性,可维护性降低,对于多变的SQL,MyBatis的动态SQL更加灵活。
  4. JDBC查询到RestSet后我们还需要对结果进行提取,而MyBatis支持将结果直接封装为JavaBean、List、Map、Java基本类型等的数据类型。

MyBatis快速入门

1、新建maven工程

2、添加maven的坐标。此处用到了mybatis、mysql-connector-java、junit、log4j

				<dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>

3、编写实体类,这里简单的编写一个User

package cn.rayfoo.bean;

import java.io.Serializable;
import java.util.Date;

/**
 * @author: rayfoo@qq.com
 * @date: 2020/6/6 1:57 上午
 * @description:
 */
public class User implements Serializable {

    /**用户id*/
    private Integer id;
    /**用户名*/
    private String username;
    /**生日*/
    private Date birthday;
    /**性别*/
    private String sex;
    /**住址*/
    private String address;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }

    public User(Integer id, String username, Date birthday, String sex, String address) {
        this.id = id;
        this.username = username;
        this.birthday = birthday;
        this.sex = sex;
        this.address = address;
    }

    public User() {
    }
}

4、编写持久层接口UserMapper

package cn.rayfoo.mapper;

import cn.rayfoo.bean.User;

import java.util.List;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/6/6 2:12 上午
 * @Description: 用户的查询接口
 */
public interface UserMapper {

    /**
     * 查询所有用户
     * @return 返回查询到的所有用户
     */
    List<User> findAllUser();
}

5、创建MyBatis全局配置文件和映射文件

<?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>
    <settings>
        <!-- 打印日志 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!-- 是否开启自动驼峰命名规则(camel case)映射,即从数据库列名 A_COLUMN 到属性名 aColumn 的类似映射 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <typeAliases>
        <package name="cn.rayfoo.bean"/>
    </typeAliases>
    <!-- 配置 mybatis 的环境 -->
    <environments default="mysql">
        <!-- 配置 mysql 的环境 -->
        <environment id="mysql">
            <!-- 配置事务的类型 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置连接数据库的信息:用的是数据源(连接池) -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 告知 mybatis 映射配置的位置 -->
    <mappers>
        <mapper resource="cn/rayfoo/mapper/UserMapper.xml"/>
        <mapper resource="cn/rayfoo/mapper/HeroMapper.xml"/>
    </mappers>
</configuration>
<?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="cn.rayfoo.mapper.UserMapper">
    <!-- 配置查询所有操作 -->
    <select id="findAllUser" resultType="cn.rayfoo.bean.User">
            select id,username,birthday,sex,address from `user`
    </select>
</mapper>

6、编写测试类进行测试

package cn.rayfoo.test;

import cn.rayfoo.bean.User;
import cn.rayfoo.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

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

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/6/6 10:01 下午
 * @Description:
 */
public class MyBatisTest {
    public static void main(String[] args) throws Exception {
        //1、读取全局配置文件
        InputStream conf = Resources.getResourceAsStream("MyBatis-cfg.xml");
        //2、创建工厂对象SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(conf);
        //3、使用工厂生产SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //4、创建接口的代理类对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        //5、执行查询方法
        List<User> allUser = userMapper.findAllUser();
        //6、查看结果
        for (User user : allUser) {
            System.out.println(user);
        }
        //7、释放资源
        sqlSession.close();
        conf.close();
    }
}

MyBatis执行流程

  1. 实现由SQLSessionFactoryBuilder加载配置文件
  2. 然后由SQLSessionFactoryBuilder构建一个SqlSessionFactory
  3. SqlSessionFactory生产SqlSession
  4. SqlSession使用Executor来调度MappedStatement执行sql语句
  5. MappedStatement会根据参数类型和返回值类型 执行并且返回对应类型的数据

![image-20200608223327582](/Users/rayfoo/Library/Application Support/typora-user-images/image-20200608223327582.png)

此处介绍的略为简单,源码级别的分析详见转自CSDN博客

MyBatis配置文件的顺序

MyBatis配置文件是有先后顺序的,如果顺序不对就会报错,所以要注意其顺序问题。

![image-20200609005353368](/Users/rayfoo/Library/Application Support/typora-user-images/image-20200609005353368.png)

配置打印SQL

在MyBatis中,可以设置打印SQL来检测代码是否正常执行。在MyBatis的全局配置文件中加入如下配置即可。

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

配置驼峰转下划线

JavaBean中标准的属性命名规则是驼峰式,但是数据库中字段命名一般都由下划线间隔,解决这个问题就需要在MyBatis中配置驼峰转下划线。在MyBatis的全局配置文件的Settings标签中加入如下配置即可。

    <settings>
        <!-- 是否开启自动驼峰命名规则(camel case)映射,即从数据库列名 A_COLUMN 到属性名 aColumn 的类似映射 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

配置别名

MyBatis中配置别名的方法如下,直接将对应包中的所有类都配置别名

    <typeAliases>
        <package name="cn.rayfoo.bean"/>
    </typeAliases>

在全局配置文件中加载Mapper文件

使用MyBatis,需要告知其mapper接口对应的配置文件的位置,这个在全局配置文件中配置即可。

如果使用了Spring框架进行整合,就无须在此处配置。

    <!-- 告知 mybatis 映射配置的位置 -->
    <mappers>
        <mapper resource="cn/rayfoo/mapper/UserMapper.xml"/>
        <mapper resource="cn/rayfoo/mapper/HeroMapper.xml"/>
    </mappers>

MyBatis中Mapper接口使用JavaBean和Map作为参数

在MyBatis中的Mapper接口方法中,支持JavaBean或者Map作为参数直接传递。使用JavaBean需要设置为parameterType的全类名,使用map则需要设置为java.util.Map。

如果配置了别名,类名和map都可以省略包名使用首字母缩写。

#{}和#{}的区别

  • {}

    • 使用#{}时,将在SQL作为一个占位符实现PreparedStatment中?的效果,占位符替换后会自动加上一对''
    • 由于是试用占位符的形式,#{}可以防止SQL注入
    • {}可以接收基本数据类型和JavaBean

    • 如果parameterType传输单个简单类型值,#{}中可以是value或者其他名称
  • ${}
    • 表示拼接SQL串
    • 通过${}可以将parameterType传入的内容拼接在SQL中且不会加上''
    • ${}可以接收基本数据类型和JavaBean
    • 如果parameterType传输单个简单类型值,#{}中只能是value
  • 使用模糊查询时,#{}必须在传参时加匹配符;使用字段名时(例如排序、分组)不能使用#{}只能使用\({},因为#{}会自动加上'',模糊查询也可以使用\){}直接拼接。

MyBatis中Mapper接口的定义规则

  1. 对应的xml文件的namespace必须和Mapper接口所在包名路径一致。
  2. 对应的xml文件id必须和接口方法名一致。
  3. parameterType必须和接口方法参数类型一致。
  4. resultType必须和接口方法返回值一致。
  5. 接口名尽量和xml文件名保持一致。

parameterType和resultType

前者用于指定输入参数类型,MyBatis通过OGNL从输入对象中获取参数值拼接在SQL中。

后者将MyBatis查询到的结果一行映射为resultType指定的类型对象;如果有多条对象则分行进行映射,并且将对象放入List容器中。

使用Map作为resultType

如果查询的结果是一个JavaBean对象,接口的返回值可以使用该Bean的类型或者Map<String,Object>来接收。如果作为Map接收,其字段名会作为key,值为value。

    /**
     * 使用JavaBean作为返回值  查询单个
     * @param custId
     * @return
     */
    Hero findHeroById(Integer custId);

    /**
     * 使用Map<String,Object>作为返回值  查询单个
     * @param custId
     * @return
     */
    Map<String,Object> findHeroById(Integer custId);
    <select id="findHeroById" resultType="cn.rayfoo.bean.Hero">
        select * from `customer` where cust_id = #{value}
    </select>

    <select id="findHeroById" resultType="java.util.Map">
        select * from `customer` where cust_id = #{value}
    </select>
JavaBean作为返回值输出结果:
ClassName{column1=value,column2=value}

Map作为返回值输出结果:
{column1=value,column2=value}

如果查询的结果是List,依旧可以使用Map<T,Object>来接收,但是必须在在Mapper的接口方法上加上@MapKey("字段名")来指定key,其中T为该字段的类型。

    /**
     * 使用Map<T,Object>作为返回值  查询多个
     * @return
     */
    @MapKey("cust_id")
    Map<Integer,Object> findAllHero();
    <select id="findAllHero" resultType="java.util.Map">
        select * from `customer`
    </select>
[val1={column1=value,column2=value},val2={column1=value,column2=value},....]

推荐:亦或是使用List<Map<String,Object>>作为返回值来接收

    /**
     * 使用List<Map<String,Object>>作为返回值  查询多个
     * @return
     */
    List<Map<String,Object>> findAllHero();
    <select id="findAllHero" resultType="java.util.Map">
        select * from `customer`
    </select>
[{column1=value,column2=value},{column1=value,column2=value}....]

其打印结果类似于json串

MyBatis中的插入、删除和更新

insert、update、delete和查询基本一致不做太多说明。

注意点:

1、插入、删除、更新时的#{}以parameterType的属性名称对应,表名后的字段与数据库字段名保持一致。

2、一定要commit,否则数据库不会更新。

3、如果插入中文乱码,请检查数据库的url是否是characterEncoding=utf8,项目的编码是否为UTF8。

4、这三者一般都不设置resultType,默认返回值都是执行sql后数据库修改的行数。

案例:

    <!--添加一个英雄-->
    <insert id="insertCustomer" parameterType="cn.rayfoo.bean.Hero">
        insert into `customer`(cust_name,cust_profession,cust_phone,email)
        values(#{custName},#{custProfession},#{custPhone},#{email})
    </insert>

MyBatis获取自增主键

方法1、在insert标签中加入子标签

<selectKey keyProperty="id" resultType="int">
            select LAST_INSERT_ID()
</selectKey>

方法2、设置insert标签的几个属性

​ useGeneratedKeys设置为true

​ keyProperty为数据库中主键字段的名称

​ keyColumn为数据库中主键字段的名称

useGeneratedKeys="true" keyProperty="id" keyColumn="id"

方法2在MyBatis3.4.0以后的版本支持批量插入获取id,使用方法和普通的插入一致,主键会自动放入parameterType对应集合中对象的每一个id中。

    public void addUserBatch(List<UserScope> list) {

        this.getSqlSession().insert("userMapper.addUserBatch",list);
        System.out.println(Arrays.toString(list.toArray()));
    }
    <insert id="addUserBatch" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
      insert user(user_name,pass_word,address) values
      <foreach collection="list" item="item" separator=",">
          ( #{item.userName},#{item.passWord},#{item.address} )
      </foreach>
    </insert>

MyBatis工具类

在使用MyBatis的过程中,我们需要频繁的加载配置文件,创建SqlSession等的繁杂过程,这就和原生JDBC一样有些累赘了,所以我们可以创建一个MyBatis工具类来简化这些重复的操作。

当然了,在和Spring整合后同样可以摒弃这些复杂的操作。

package cn.rayfoo.util;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/6/8 10:37 下午
 * @Description: Mybatis工具类
 */
public class MyBatisUtils {

    /**
     *  加载配置文件
     */
    private static InputStream conf = null;

    /**
     * SqlSession工厂
     */
    private static SqlSessionFactory sqlSessionFactory = null;
    
    /**
     * SqlSession
     */
    public static SqlSession sqlSession = null;

    /**
     * Session状态,true为开启中,false为已经关闭
     */
    private static boolean sessionStatus = true;

    static {
        //1、读取全局配置文件
        try {
            conf = Resources.getResourceAsStream("MyBatis-cfg.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        //2、创建工厂对象SqlSessionFactory
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(conf);
        //3、使用工厂生产SqlSession
        sqlSession = sqlSessionFactory.openSession();
    }

    /**
     * 获取工厂信息
     * @param clazz Mapper的字节码文件
     * @return
     */
    public static Object getMapper(Class clazz){
        //1、如果Session为空或者已经关闭,则重新生产一个Session
        if(!sessionStatus){
            sqlSession = sqlSessionFactory.openSession();
        }
        //返回Mapper
        return sqlSession.getMapper(clazz);
    }

    /**
     * 返回一个sqlSession
     * @return
     */
    public static SqlSession getSqlSession(){
        //1、如果Session为空或者已经关闭,则重新生产一个Session
        if(!sessionStatus){
            sqlSession = sqlSessionFactory.openSession();
        }
        return sqlSession;
    }
    
    /**
     * 提交、销毁SqlSession
     */
    public static void destory(){
        //提交、关闭sqlSession
        sqlSession.commit();
        sqlSession.close();
        try {
            //关闭文件流
            conf.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        sessionStatus = false;
    }
    
    private MyBatisUtils(){
        
    }
}

测试工具类

package cn.rayfoo.test;

import cn.rayfoo.bean.Hero;
import cn.rayfoo.mapper.HeroMapper;
import cn.rayfoo.util.MyBatisUtils;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/6/6 10:01 下午
 * @Description: 测试类
 */
public class MyBatisTest {
    public static void main(String[] args) throws Exception {
        //获取Mapper
        HeroMapper mapper = (HeroMapper) MyBatisUtils.getMapper(HeroMapper.class);
        //创建一个英雄
        Hero hero = new Hero("龙女","战士","10086","rayfoo@qq.com");
        //添加英雄
        mapper.insertCustomer(hero);
        //提交事务
        MyBatisUtils.destory();
    }
}

Mapper接口中多参数问题

在开发过程中Mapper接口难免会遇到需要多个参数的时候,那么此时在xml中如何映射呢?

1、在MyBatis中,Mapper接口中的方法如果使用了多个参数,MyBatis会将这些参数打包到一个Map中,Map的key是param1、param2。。。以此类推,值也就是参数对应的值。MyBatis也允许使用#{arg0}、#{arg1}。。。的形式,如果是对象类型,就要使用#{param1.属性名}

2、MyBatis中还可以使用@Param注解来指定该属性的Key值,直接在参数之前加上@Param("key")即可。如果指定自定义key,仍然可以使用param1、param2。。的形式,但是不再允许使用arg0、arg1。。的形式了。

3、注意:尽量不要使用arg0、arg1。。。的形式,param是从1开始,arg是从0开始。

resultMap

resultType使用JavaBean作为返回值时,要求JavaBean的属性名和数据库字段名保持一致(驼峰转下划线为特例),使用resultMap可以解决这个问题。它的一大作用就是来映射数据库的字段名和JavaBean的属性名。如果查询时只想查询部分字段,同样可以使用resultMap来实现。

  1. 在Mapper接口对应的XML文件中加入resultMap标签,一般写在xml的最上方且命名为javaBean首字母小写+Map的形式。
  2. 其id属性必须唯一,可以被其他标签的resultMap属性指定。
  3. type属性用于指定其对应的JavaBean的全类名(或者别名,但必须配置别名)。
  4. id子标签用于指定数据库中的主键
  5. result子标签用于指定数据库中普通字段
  6. 如果映射的不是全部字段,则查询时不会查询没有被映射的字段。
  7. id和result都存在column和property两个属性,其中column映射数据库字段名,property映射JavaBean的属性名。
  8. id和result都存在javaType和jdbcType两个属性,分别用来指定该标签中数据库和Java类型的属性,如果是一般类型的数据,这两个属性可以省略。
    <resultMap id="heroMap" type="cn.rayfoo.bean.Hero">
        <id column="cust_id" property="custId"></id>
        <result column="cust_name" property="custName"></result>
        <result column="cust_phone" property="custPhone"></result>
        <result column="cust_profession" property="custProfession"></result>
        <result column="email" property="email"></result>
    </resultMap>

多表操作

一对多关系

在数据库中,经常会遇到一对多查询,那么常见的一对多关系比如部门>员工,班级>学员。。等等。遇到这类的数据库关系,就需要进行一对多映射。

一对多关系如何在数据库中建立?

在一对多关系中,通常会在多的一方建立一个外键用于保存一的一方的主键。

例如已有客户表,现在建立一个订单表,那么客户表无需更改,只需要在新建立的订单表中加入order原有的"order_id"、“order_name”、“order_num”以及"cust_id"四个字段即可。其中“cust_id”添加外键约束。

一对一关系如何在Java中映射?

在java类中,如果要映射一对多关系,一般是在多的一方中取消一对多中的“一方”外键属性的声明,使用一对多中的“一方”所在的java类取代。

public class Order {

    /** 订单号 */
    private Integer orderId;

    /** 订单名称 */
    private String orderName;

    /** 订单金额 */
    private Double orderPrice;

    /** 下单人 */
    private Customer customer;
  
  	getter setter 构造...
}
一对多关系如何在MyBatis中映射?
MyBatis中映射对多关系有多种方法,其中比较简单易于理解的就是使用连缀的方式,映射多对一中的“一方”的字段和属性。
    <resultMap id="baseResultMap" type="cn.rayfoo.bean.Order">
        <id property="orderId" column="order_id"></id>
        <result property="orderName" column="order_name"></result>
        <result property="orderPrice" column="order_price"></result>
        <result property="customer.custId" column="cust_id"></result>
        <result property="customer.custName" column="cust_name"></result>
        <result property="customer.custProfession" column="cust_profession"></result>
        <result property="customer.custPhone" column="cust_phone"></result>
    </resultMap>

案例:

    <select id="findOrders" resultMap="baseResultMap">
        select * from `order` o left join `customer` c on o.cust_id = c.cust_id
    </select>
public interface OrderMapper {

    List<Order> findOrders();

}
public class MyBatisTest {
    public static void main(String[] args) throws Exception {
        //获取Mapper
        OrderMapper mapper = (OrderMapper) MyBatisUtils.getMapper(OrderMapper.class);
        //获取全部订单
        List<Order> orders = mapper.findOrders();
        for (Order order : orders) {
            System.out.println(order);
        }
        //销毁
        MyBatisUtils.destory();
    }
}
不映射直接使用Map同样可以解多对一多映射问题
    <select id="findAllOrder" resultType="java.util.Map">
        select * from `order` o left join `customer` c on o.cust_id = c.cust_id
    </select>
public interface OrderMapper {

    List<Map<String,Object>> findAllOrder();

}
public class MyBatisTest {
    public static void main(String[] args) throws Exception {
        //获取Mapper
        OrderMapper mapper = (OrderMapper) MyBatisUtils.getMapper(OrderMapper.class);
        //获取全部订单
        List<Map<String, Object>> allOrder = mapper.findAllOrder();
        for (Map<String, Object> item : allOrder) {
            System.out.println(item);
        }
        //销毁
        MyBatisUtils.destory();
    }
}
{cust_profession=战士, order_price=1536.29, cust_name=龙女, order_id=1, cust_id=13, cust_phone=10086, email=rayfoo@qq.com, order_name=花呗还款}
{cust_profession=战士, order_price=4000.0, cust_name=龙女, order_id=2, cust_id=14, cust_phone=10086, email=rayfoo@qq.com, order_name=基金加仓}
{cust_profession=战士, order_price=3298.0, cust_name=龙女, order_id=3, cust_id=13, cust_phone=10086, email=rayfoo@qq.com, order_name=购买手机}
使用association映射多对一关系

MyBatis提供的对多映射的另一种方式就是使用association,在resultMap中加入association子标签,在该标签中添加property和javaType,分别映射一对多中的“一方”在java类中的属性名,和其类型。在association中可以继续添加id和result来映射一对多中的“一方”。

    <resultMap id="baseResultMap" type="cn.rayfoo.bean.Order">
        <id property="orderId" column="order_id"></id>
        <result property="orderName" column="order_name"></result>
        <result property="orderPrice" column="order_price"></result>
        <association property="customer" javaType="cn.rayfoo.bean.Customer">
            <id property="custId" column="cust_id"></id>
            <result property="custName" column="cust_name"></result>
            <result property="custProfession" column="cust_profession"></result>
            <result property="custPhone" column="cust_phone"></result>
        </association>
    </resultMap>
association级联查询-分步查询

在association标签中,存在一个select属性,其值可以指定一个查询方法的id,如果该查询需要使用到参数,还可以指定column属性为查询参数。

    <resultMap id="baseResultMap" type="cn.rayfoo.bean.Order">
        <id property="orderId" column="order_id"></id>
        <result property="orderName" column="order_name"></result>
        <result property="orderPrice" column="order_price"></result>
        <association property="customer" javaType="cn.rayfoo.bean.Customer" 
                     select="cn.rayfoo.mapper.CustomerMapper.findCustomer" column="cust_id">
        </association>
    </resultMap>
association级联查询-分步查询-懒加载

使用association进行分步查询,默认是查询时一定会执行select中的语句,无论是否用到了association中的元素。开启懒加载可以实现当用到association中的元素时执行才执行其对应的select语句。

修改mybatis的全局配置文件即可,当mybatis3.4.1以前都为true,之后的版本默认为false

但是当调用toString方法时,不管是否使用了association中映射的属性值,都会触发延迟加载,去掉lazyLoadTriggerMethods的value中的toString即可。

    <settings>
        <!-- 打印日志 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!-- 是否开启自动驼峰命名规则(camel case)映射,即从数据库列名 A_COLUMN 到属性名 aColumn 的类似映射 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 是否开启懒加载  当false时为按需加载 -->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!-- 哪些方法会触发延迟加载 -->
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode"/>
    </settings>
一对多的添加操作

对于一对多映射中的添加操作,应该先添加一的一方获取到一的一方的id才能添加多的一方。例如存在客户表和订单表,要进行客户和订单的添加,那么就需要先添加客户,获取客户的自增id,才能进而添加订单。

    <insert id="insertCustomer" parameterType="cn.rayfoo.bean.Customer" useGeneratedKeys="true" keyColumn="cust_id" keyProperty="custId">
        insert into `customer`(cust_name,cust_profession,cust_phone,email)
        values(#{custName},#{custProfession},#{custPhone},#{email})
    </insert>
    <insert id="insertOrder" parameterType="cn.rayfoo.bean.Order">
        insert into `order`(order_name,order_price,cust_id)
        values(#{orderName},#{orderPrice},#{customer.custId})
    </insert>
package cn.rayfoo.test;

import cn.rayfoo.bean.Customer;
import cn.rayfoo.bean.Order;
import cn.rayfoo.mapper.CustomerMapper;
import cn.rayfoo.mapper.OrderMapper;
import cn.rayfoo.util.MyBatisUtils;

import java.util.List;
import java.util.Map;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/6/6 10:01 下午
 * @Description: 测试类
 */
public class MyBatisTest {
    public static void main(String[] args) throws Exception {
        //获取Mapper
        CustomerMapper customerMapper = (CustomerMapper) MyBatisUtils.getMapper(CustomerMapper.class);
        OrderMapper orderMapper = (OrderMapper) MyBatisUtils.getMapper(OrderMapper.class);
        //创建顾客
        Customer customer = new Customer("盖伦","战士","10011","rayfooQqq.com");
        customerMapper.insertCustomer(customer);
        //创建订单
        Order order1 = new Order("MacBook Pro",888.88,customer);
        Order order2 = new Order("MacBook Air",999.99,customer);
        orderMapper.insertOrder(order1);
        orderMapper.insertOrder(order2);
        //销毁
        MyBatisUtils.destory();
    }
}

一对多的查询操作

当映射的一方是多的一方,JavaBean中使用List来映射,resultMap也不能使用association了,association替换为collection,javaType替换为ofType,其余都没有变化。

分步查询并不见得更快,要结合实际的使用情况,分步查询会发送更多次的sql(每一条数据都会发送一条sql,相当于sql的循环调用),但是分步查询支持懒加载。

    <resultMap id="baseResultMap" type="cn.rayfoo.bean.Customer">
        <id property="custId" column="cust_id"></id>
        <result property="custName" column="cust_name"></result>
        <result property="custProfession" column="cust_profession"></result>
        <result property="custPhone" column="cust_phone"></result>
        <result property="email" column="email"></result>
        <collection property="orders" ofType="cn.rayfoo.bean.Order">
            <id property="orderId" column="order_id"></id>
            <result property="orderName" column="order_name"></result>
            <result property="orderPrice" column="order_price"></result>
        </collection>
    </resultMap>

    <select id="findAllCustomer" resultMap="baseResultMap">
        select * from `customer` as c left join `order` as o on c.cust_id = o.cust_id
    </select>
package cn.rayfoo.test;

import cn.rayfoo.bean.Customer;
import cn.rayfoo.mapper.CustomerMapper;
import cn.rayfoo.util.MyBatisUtils;

import java.util.List;
import java.util.Map;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/6/6 10:01 下午
 * @Description: 测试类
 */
public class MyBatisTest {
    public static void main(String[] args) throws Exception {
        //获取Mapper
        CustomerMapper customerMapper = (CustomerMapper) MyBatisUtils.getMapper(CustomerMapper.class);
        List<Customer> allCustomer = customerMapper.findAllCustomer();
        for (Customer customer : allCustomer) {
            System.out.print(customer);
        }
        //销毁
        MyBatisUtils.destory();
    }
}

一对多分步查询(不建议使用)
    <resultMap id="baseResultMap" type="cn.rayfoo.bean.Customer">
        <id property="custId" column="cust_id"></id>
        <result property="custName" column="cust_name"></result>
        <result property="custProfession" column="cust_profession"></result>
        <result property="custPhone" column="cust_phone"></result>
        <result property="email" column="email"></result>
        <collection property="orders" ofType="java.util.List" select="cn.rayfoo.mapper.OrderMapper.findOrderById" column="cust_id">
        </collection>
    </resultMap>
    <select id="findOrderById" resultType="cn.rayfoo.bean.Order">
        select * from `order` where order_id = #{value}
    </select>
    /**
     * 根据cust_id查询
     * @param cust_id
     * @return
     */
    List<Order> findOrderById(Integer cust_id);
package cn.rayfoo.test;

import cn.rayfoo.bean.Customer;
import cn.rayfoo.bean.Order;
import cn.rayfoo.mapper.CustomerMapper;
import cn.rayfoo.mapper.OrderMapper;
import cn.rayfoo.util.MyBatisUtils;

import java.util.List;
import java.util.Map;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/6/6 10:01 下午
 * @Description: 测试类
 */
public class MyBatisTest {
    public static void main(String[] args) throws Exception {
        //获取Mapper
        CustomerMapper customerMapper = (CustomerMapper) MyBatisUtils.getMapper(CustomerMapper.class);
        List<Customer> allCustomer = customerMapper.findAllCustomer();
        for (Customer customer : allCustomer) {
            System.out.println(customer);
        }
        //销毁
        MyBatisUtils.destory();
    }
}

一对多删除

此时需要注意,如果用户已经不存在了,那么它对应的订单就是垃圾数据了,此时删除用户后应该同时删除掉用户的订单数据。

如果只想删除客户,不删除其关联的订单,可以先打破订单和客户之间的关系。使用update语句将符合条件的客户编号外键改为null即可。

多对多关系

在数据库中,也会遇到一对多查询,那么常见的多对多关系比如老师-学生,学生-课程,用户-角色。。等等。遇到这类的数据库关系,就需要进行多对多映射。

多对多建表原则

多对多关系,一般会建立一个中间表,中间表中创建两个外键存储两个表的主键。

例如现在存在学生表、教师表、则需要创建中间表教师_学生。

-- student table
create table student
(
    student_id   int auto_increment
        primary key,
    student_name varchar(20) null
);
-- teacher table
create table teacher
(
    teacher_id   int auto_increment
        primary key,
    teacher_name varchar(20) null
);
-- teacher_student table
create table teacher_student
(
    id         int auto_increment
        primary key,
    student_id int null,
    teacher_id int null,
    constraint teacher_student_student_student_id_fk
        foreign key (student_id) references student (student_id),
    constraint teacher_student_teacher_teacher_id_fk
        foreign key (teacher_id) references teacher (teacher_id)
);

在javaBean中创建实体时,无需创建中间表的Bean对象。只需要创建Student、Teacher即可。

在MyBatis的映射中,我们可以将多对多转换为两个一对多,在两方别进行对多映射即可。和一对多的不同之处只有SQL语句。多对多同样支持分步查询。

多对多的添加分为三步,添加学生、添加老师、添加中间表。关系表的插入可以在学生、老师任意一段进行。

    /**
     * 添加一个学生
     * @param student
     */
    void insertStudent(Student student);

    /**
     * 更新学生和老师的关系
     * @param teacherId
     * @param studentId
     */
    void updateRelation(@Param("teacherId") Integer teacherId, @Param("studentId") Integer studentId);
    /**
     * 添加一个教师
     * @param teacher
     */
    void insertTeacher(Teacher teacher);
    <insert id="insertStudent" parameterType="cn.rayfoo.bean.Student" useGeneratedKeys="true" keyColumn="student_id" keyProperty="studentId">
       insert into student(student_name) values(#{studentName})
    </insert>
    <insert id="updateRelation">
        insert into teacher_student(teacher_id,student_id)
        values(#{teacherId},#{studentId})
    </insert>
    <insert id="insertTeacher" parameterType="cn.rayfoo.bean.Teacher" useGeneratedKeys="true" keyColumn="teacher_id" keyProperty="teacherId">
        insert into teacher(teacher_name) values(#{teacherName})
    </insert>
package cn.rayfoo.test;

import cn.rayfoo.bean.Customer;
import cn.rayfoo.bean.Order;
import cn.rayfoo.bean.Student;
import cn.rayfoo.bean.Teacher;
import cn.rayfoo.mapper.CustomerMapper;
import cn.rayfoo.mapper.OrderMapper;
import cn.rayfoo.mapper.StudentMapper;
import cn.rayfoo.mapper.TeacherMapper;
import cn.rayfoo.util.MyBatisUtils;

import java.util.List;
import java.util.Map;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/6/6 10:01 下午
 * @Description: 测试类
 */
public class MyBatisTest {
    public static void main(String[] args) throws Exception {
        //获取Mapper
        TeacherMapper teacherMapper = (TeacherMapper) MyBatisUtils.getMapper(TeacherMapper.class);
        StudentMapper studentMapper = (StudentMapper) MyBatisUtils.getMapper(StudentMapper.class);
        //创建学生和老师
        Student student = new Student("全浩");
        Teacher teacher1 = new Teacher("李怡霖");
        Teacher teacher2 = new Teacher("闫洪波");
        //添加
        studentMapper.insertStudent(student);
        teacherMapper.insertTeacher(teacher1);
        teacherMapper.insertTeacher(teacher2);
        //更新关系
        studentMapper.updateRelation(teacher1.getTeacherId(),student.getStudentId());
        studentMapper.updateRelation(teacher2.getTeacherId(),student.getStudentId());
        //销毁
        MyBatisUtils.destory();
    }
}

MySQL insert语句中中value和values的区别

这里补充一个知识点,在MySQL的insert语句中使用value和values都可以,建议在插入单行的时候使用VALUES,在插入多行的时候使用VALUE;在大多数情况下使用这样的方案效率比较高。而SQL Server中只支持values关键字。

动态SQL

MyBatis中提供了动态SQL的机制,所谓动态sql其实就是动态的拼接sql。MyBatis为我们提供了很多的标签,可以实现类似于java代码中的选择、分支、 循环等功能。通过这些标签可以动态的拼接sql。

if标签

if标签用于判断一个布尔值或者布尔表达式,来判定是否拼接标签内的SQL。一般用于where后条件的非空、忽略等判断。一般都会配合where标签一起使用。

where标签

在使用if标签的过程中会遇到一些问题,比如当if的test都为false时,就会多出一个where标签,当第一个条件为空时,就会多出一个and标签。此时就需要用到where标签

where标签可以自动的生成和删除where关键字、还可以删除where后第一个and关键字。

所以在使用where和if标签的时候 , 尽量使用前置and,不要使用后置and。

trim标签

此标签可以代替where标签来使用,主要的功能是对前缀、后缀的一些操作。

    <select id="selectByCondition" resultType="cn.rayfoo.bean.Customer">
        select * from `customer`
        <trim prefix="where" prefixOverrides="and" suffixOverrides="and">
            <if test="custName != null and custName != ''">
                and cust_name = #{custName}
            </if>
            <if test="profession != null and profession != ''">
                and profession = #{profession} and
            </if>
        </trim>
    </select>

此案例中,会在条件之前增加where,去掉条件之前的and、条件之后的and。

choose when otherwise标签

这个标签类似于java中的if else

当when中test的条件成立就只执行when中的语句,不再继续执行。否则执行otherwise中的内容。

其中when可以存在多个,otherwise只能存在一个。

    <select id="selectByCondition" resultType="cn.rayfoo.bean.Customer">
        select * from `customer`
        <trim prefix="where" prefixOverrides="and" suffixOverrides="and">
            <choose>
                <when test="custName != null and custName != ''">
                    and custName = #{cust_name}
                </when>
                <when test="profession != null and profession !=''">
                    and profession = #{profession}
                </when>
                <otherwise>
                    and 1=1
                </otherwise>
            </choose>
        </trim>
    </select>

set标签

在update的操作中 set后同样可以使用if来进行判断,此时如果存在多个if,就可能会出现多出“逗号”的情况,此时可以使用set标签来代替set关键字,set标签可以帮我们去掉最后一个逗号。一般使用set标签时,逗号都放在后面。

    <update id="updateCustomer">
        update `customer`
        <set>
            <if test="custName != null and custName != ''">
                cust_name = #{custName},
            </if>
            <if test="custProfession != null and custProfession != ''">
                cust_profession = #{custProfession}
            </if>
        </set>
        <where>
            cust_id = #{custId}
        </where>
    </update>

foreach标签

当查询的内容是多个时,可以使用foreach标签。foreach标签可以遍历list或者array类型的数据。

collection属性:指定参数类型,可选list和array,如果是array,那mybatis标签中的参数类型需要指定为对应类型的数组形式,例如parameterType="Integer[]",list则需要设置为java.util.List

open属性:开始标志一般用(

close属性:结束标志一般用)

separator属性:间隔符 一般用逗号

item属性:迭代变量 任意名称

在标签体内可以直接使用#{item对应的值}进行遍历

    <select id="findCustomers" parameterType="list" resultType="cn.rayfoo.bean.Customer">
        select * from `customer`
        <where>
            `cust_id` in
            <foreach collection="list" open="(" close=")" separator="," item="id">
                #{id}
            </foreach>
        </where>
    </select>
foreach的高级用法:如果要遍历一个bean对象中的array或者list类型的属性,需要先将parameterType=指定为该对象。collection则指定此属性 其他用法相同。
    <select id="findCustomers" parameterType="cn.rayfoo.Customer" resultType="cn.rayfoo.bean.Customer">
        select * from `customer`
        <where>
            `cust_id` in
            <foreach collection="ids" open="(" close=")" separator="," item="id">
                #{id}
            </foreach>
        </where>
    </select>

bind标签

该标签可以取出传入的值,重新处理,赋值给另一个值。

注意点:如果是普通类型的单值,必须加@Param注解,才能正常取出。如果是对象直接可以获取。

SQL片段

动态SQL支持将一段SQL封装为一个片段,此片段可以在多处执行。

用法:使用标签创建sql片段,使用引入。

sql片段中可以使用choose、if、等的逻辑判断

缓存机制

MyBatis中使用了缓存机制来提高性能,当查询数据时,会先从缓存中取出数据,如果缓存中没有,再到数据库中查询。

MyBatis中的缓存有两种:一级缓存和二级缓存

一级缓存

一级缓存是SqlSession(一次会话)级别的,二级缓存是Mapper级别的。

一级缓存默认是开启的状态,在SqlSession没有关闭之前,再去查询时,会从缓存中取出数据,不会重新发送新的sql。

一级缓存的失效:发生如下场景时,一级缓存会失效。

  • 如果在下次查询之前进行了增、删、改操作,缓存就会失效。
  • 手动清空了缓存(sqlSession.clearCache()方法)
  • 两次查询的条件不一样,缓存也会失效
  • 如果两个查询在不同的SqlSession当中

二级缓存

二级缓存是全局作用域缓存,一个namespace对应一个缓存,如果会话关闭,一级缓存的数据会被保存到二级缓存中,不同的namespace查出的数据,也会放到自己对应的缓存中,默认也是打开的,早期版本呢是false。

二级缓存使用的注意事项

1、确保全局配置文件中已经打开了二级缓存,settings->setting->cacheEnable == true

2、在对应的mapper中添加cache标签

​ 属性介绍

​ eviction:回收策略LRU(默认)【移除最近最少使用】、FIFO【先进先出】、SOFT、WEAK

​ flushInterval:刷新间距,单位是毫秒,默认不清空

​ readOnly:是否只读,取值为true或false,为true是只读mybatis会直接给缓存的引用不安全,但是速度快,非只读mybatis会利用序列化和反序列化复制一份 速度慢

​ size:可以存放多少个元素 默认的即可,也不能设置的太大,太大容易溢出

​ type:可以用来指定自定义的缓存,例如redis

3、JavaBean要实现Serializable接口,因为readOnly设置为只读的话,需要进行复制操作。

注意:只要会话关闭后一级缓存中数据才会保存到二级缓存中!

缓存的相关配置

是否可以设置每个mapper方法的缓存是否开启?

在MyBatis的mapper方法中,可以单独的设置是否需要开启缓存,它的优先级是最高的。但是它只能控制二级缓存是否有用,无法影响一级缓存。

如何清空二级缓存?

增删改查标签中存在一个flushCache属性,默认为true,作用是清空一级、二级缓存。

可以手动调用sqlSession.clearCache()手动清空一级缓存。

如何禁用一级缓存?

在settings->setting->localCacheScope设置为STATEMENT,默认为SESSION,设置为STATEMENT可以关闭一级缓存。

缓存使用顺序

1、先到二级缓存找

2、二级缓存没有就去一级缓存找

3、一级缓存也没有就去数据库中找

posted @ 2020-06-17 16:56  张瑞丰  阅读(550)  评论(0编辑  收藏  举报