MyBatis

MyBatis

文章目录

      • MyBatis
        • 一、Mybatis简介
        • 二、初体验
        • 三、映射文件解析
        • 四、DAO接口的使用
        • 五、关联查询`[难点]`
          • 5.1 表之间的关系
          • 5.2 多对一查询(一对一)
          • 5.3 一对多查询(多对多)
          • 5.4 查询过程中涉及的缓存
          • 5.5 ResultMap的继承
          • 5.6 级联添加
        • 六、动态SQL`[重点]`
          • 6.1 if
          • 6.2 choose、when、otherwise
          • 6.3 where、set
          • 6.4 trim`[难点]`
          • 6.5 foreach
        • 七、Mybatis中注解的使用
          • 7.1 @Param
            • 7.2 @select、@update、@delete、@insert等注解

一、Mybatis简介

是一个持久化框架。是一个半自动的ORM框架。前面的版本叫ibatis,后面迁移到google code时更名为Mybatis,现在已经迁移到github上。

持久化:将一个转瞬即逝的对象保存下来。在软件行业中,持久化一般指将内存中的数据保存到文件(数据库文件)中。

ORM:object relative mapping对象关系映射。对象:Java实体类对象,关系:关系型数据库。映射:关联。

简单来说,就是指将数据库中的某一个表,与Java实体类相关联,表中字段与实体类的属性相关联。

半自动:针对全自动而言的,是指在操作数据库时,需要编写相应的SQL语句,指定相应的映射关系,结果会自动映射。

二、初体验

1、新建maven工程,导入依赖

<dependencies>
    <dependency>
        <groupId>org.mybatisgroupId>
        <artifactId>mybatisartifactId>
        <version>3.4.6version>
    dependency>
    <dependency>
        <groupId>junitgroupId>
        <artifactId>junitartifactId>
        <version>4.12version>
        <scope>testscope>
    dependency>
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <version>5.1.40version>
    dependency>
dependencies>

2、编写核心配置文件


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">transactionManager>
            
            <dataSource type="POOLED">
                
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://localhost:3306/myshop?useUnicode=true&characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            dataSource>
        environment>
    environments>

    
    <mappers>
        <mapper resource="ProductMapper.xml">mapper>
    mappers>
configuration>

3、编写映射文件


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="a">
    
    <select id="b" resultType="java.lang.Long">
      SELECT COUNT(1) FROM product
    select>
mapper>

4、编写测试代码

package com.qf.day52.test;

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 org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;

public class ProductDAOTest {
    private SqlSessionFactory factory;
    // 在下面的测试方法调用之前调用
    @Before
    public void before(){
        try {
            // 读取核心配置文件
            InputStream inputStream = Resources.getResourceAsStream("mybatisCfg.xml");
            // 创建连接工厂(会话工厂)、工厂模式
            factory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testCount(){
        // 获得连接
        SqlSession session = factory.openSession();
        // 数据库操作
        Long count = session.selectOne("a.b");
        System.out.println(count);
        // 关闭连接
        session.close();
    }
}

三、映射文件解析

注意:

1、Mybatis其实不需要实体类和DAO就可以实现数据库操作。

2、实体类的作用:a、封装传入的多个参数(Mybatis框架只允许传一个参数)。b、封装查询结果。

3、DAO的作用:


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="ProductDAO">
    
    <select id="count" resultType="java.lang.Long">
      SELECT COUNT(1) FROM product
    select>

    
    <delete id="delete" parameterType="java.lang.Integer">
        DELETE FROM product WHERE p_id = #{id}
    delete>
mapper>
// 注意,增删改操作需要提交事务
@Test
public void testDelete(){
    // 获得连接
    SqlSession session = factory.openSession();
    // 数据库操作
    // count表示所影响的行数
    int count = session.delete("ProductDAO.delete", 27);
    System.out.println(count);
    // 提交事务
    session.commit();
    // 关闭连接
    session.close();
}

DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="ProductDAO">
    
    <sql id="productColumns">
        p_id, t_id, p_name, p_time, p_image, p_price, p_state, p_info
    sql>
    
    <select id="count" resultType="java.lang.Long">
      SELECT COUNT(1) FROM product
    select>

    
    <delete id="delete" parameterType="java.lang.Integer">
        DELETE FROM product WHERE p_id = #{id}
    delete>

    
    <insert id="save">
        INSERT INTO product(t_id, p_name, p_time, p_image, p_price, p_state, p_info)
        VALUES (#{tid}, #{pname}, #{ptime}, #{pimage}, #{pprice}, #{pstate}, #{pinfo})
    insert>

    
    <update id="update">
        UPDATE  product SET t_id = #{tid}, p_name = #{pname}, p_time =  #{ptime},
        p_image = #{pimage}, p_price = #{pprice}, p_state = #{pstate}, p_info = #{pinfo}
        WHERE p_id = #{pid}
    update>

    
    <resultMap id="productMap" type="com.qf.day52.entity.Product">
        
        
        <id property="pid" column="p_id">id>
        
        <result property="tid" column="t_id">result>
        <result property="pname" column="p_name">result>
        <result property="ptime" column="p_time">result>
        <result property="pimage" column="p_image">result>
        <result property="pprice" column="p_price">result>
        <result property="pstate" column="p_state">result>
        <result property="pinfo" column="p_info">result>
    resultMap>
    
    <select id="findAll" resultMap="productMap">
        SELECT
        <include refid="productColumns">include>
        FROM product
    select>

    
    <select id="findById" resultMap="productMap">
      SELECT
      <include refid="productColumns">include>
        FROM product WHERE p_id = #{pid}
    select>
mapper>
public class ProductDAOTest {
    private SqlSessionFactory factory;
    // 在下面的测试方法调用之前调用
    @Before
    public void before(){
        try {
            // 读取核心配置文件
            InputStream inputStream = Resources.getResourceAsStream("mybatisCfg.xml");
            // 创建连接工厂(会话工厂)、工厂模式
            factory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testCount(){
        // 获得连接
        SqlSession session = factory.openSession();
        // 数据库操作
        Long count = session.selectOne("ProductDAO.count");
        System.out.println(count);
        // 关闭连接
        session.close();
    }

    // 注意,增删改操作需要提交事务
    @Test
    public void testDelete(){
        // 获得连接
        SqlSession session = factory.openSession();
        // 数据库操作
        // count表示所影响的行数
        int count = session.delete("ProductDAO.delete", 27);
        System.out.println(count);
        // 提交事务
        session.commit();
        // 关闭连接
        session.close();
    }

    @Test
    public void testSave(){
        // 获得连接
        SqlSession session = factory.openSession();
        // 数据库操作
        // 由于Mybatis只能传一个参数,所以当有多个参数需要传递时,可以将其封装成实体类对象或者Map

        /*********************************** 第一种方式:使用map*************************
        Map map = new HashMap();
        // #{tid}, #{pname}, #{ptime}, #{pimage}, #{pprice}, #{pstate}, #{pinfo}
        // 此处map中应该使用tid等#{}中的名称作为key,要传进去的内容作为值
        map.put("tid", 1);
        map.put("pname", "华为P40");
        map.put("ptime", new Date());
        map.put("pimage", "aaaa");
        map.put("pprice", 30);
        map.put("pstate", 1);
        map.put("pinfo", "好");
        session.insert("ProductDAO.save", map);
        ******************************第一种方式end*******************************/

        /**************************第二种方式:使用实体类(常用方式)*************/
        // #{tid}, #{pname}, #{ptime}, #{pimage}, #{pprice}, #{pstate}, #{pinfo}
        // 此处应该使用tid等#{}中的名称作为属性名称,要传进去的内容作为属性值
        Product product = new Product();
        product.setTid(2);
        product.setPname("华为P40 pro");
        product.setPimage("bbbb");
        product.setPtime(new Date());
        product.setPprice(40.0);
        product.setPstate(1);
        product.setPinfo("非常好");
        session.insert("ProductDAO.save", product);
        /***************************第二种方式end*********************************/
        // 提交事务
        session.commit();
        // 关闭连接
        session.close();
    }

    @Test
    public void update(){
        // 获得连接
        SqlSession session = factory.openSession();
        // 数据库操作
        Product product = new Product();
        product.setPid(28);
        product.setTid(2);
        product.setPname("华为P40 pro");
        product.setPimage("bbbb");
        product.setPtime(new Date());
        product.setPprice(40.0);
        product.setPstate(1);
        product.setPinfo("非常好");
        session.insert("ProductDAO.update", product);
        // 提交事务
        session.commit();
        // 关闭连接
        session.close();
    }

    @Test
    public void testFindAll(){
        // 获得连接
        SqlSession session = factory.openSession();
        // 数据库操作
        List<Product> list = session.selectList("ProductDAO.findAll");
        System.out.println(list);
        // 关闭连接
        session.close();
    }

    @Test
    public void testFindById(){
        // 获得连接
        SqlSession session = factory.openSession();
        // 数据库操作
        Product product = session.selectOne("ProductDAO.findById", 1);
        System.out.println(product);
        // 关闭连接
        session.close();
    }
}

四、DAO接口的使用

使用Mybatis框架,可以不需要使用DAO,只要使用映射文件即可。

但是我们发现,有一些问题:

1、不能根据方法名称了解该方法作用。

2、对于在映射文件中定义的方法名称,使用字符串的形式调用,无法有效编译提示正确性。

3、对于方法参数类型无法有效提示。

4、对于方法的返回值类型,也无法有效提示。

为了让代码编写过程中,代码可以有效的编译提示,我们用反射的方式,使用接口来映射mapper文件中的方法。

package com.qf.day52.dao;

import com.qf.day52.entity.Product;

import java.util.List;

// 此接口作用为映射mapper文件
// 方法名称对应操作的id
// 参数和返回值类型也需要一一对应
// 接口需要对应namespace,注意应该全名称对应
public interface ProductDAO {
    Long count();
    int delete(Integer id);
    int save(Product product);
    int update(Product product);
    List<Product> findAll();
    Product findById(Integer id);
}
package com.qf.day52.test;

import com.qf.day52.dao.ProductDAO;
import com.qf.day52.entity.Product;
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 org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

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

public class ProductDAOTest1 {
    private SqlSessionFactory factory;
    // 在下面的测试方法调用之前调用
    @Before
    public void before(){
        try {
            // 读取核心配置文件
            InputStream inputStream = Resources.getResourceAsStream("mybatisCfg.xml");
            // 创建连接工厂(会话工厂)、工厂模式
            factory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testCount(){
        // 获取连接
        SqlSession session = factory.openSession();
        // 通过连接获取接口的映射对象
        ProductDAO productDAO = session.getMapper(ProductDAO.class);
        // 数据库操作
        // 使用断言
        Assert.assertTrue(productDAO.count() > 0);
        // 关闭连接
        session.close();
    }

    // 注意,增删改操作需要提交事务
    @Test
    public void testDelete(){
        // 获得连接
        SqlSession session = factory.openSession();
        // 数据库操作
        // 通过连接获取接口的映射对象
        ProductDAO productDAO = session.getMapper(ProductDAO.class);
        Assert.assertTrue(productDAO.delete(29) == 1);
        // 提交事务
        session.commit();
        // 关闭连接
        session.close();
    }

    @Test
    public void testSave(){
        // 获得连接
        SqlSession session = factory.openSession();
        // 数据库操作
        Product product = new Product();
        product.setTid(2);
        product.setPname("华为P40 pro");
        product.setPimage("bbbb");
        product.setPtime(new Date());
        product.setPprice(40.0);
        product.setPstate(1);
        product.setPinfo("非常好");
        ProductDAO productDAO = session.getMapper(ProductDAO.class);
        Assert.assertTrue(productDAO.save(product)==1);
        // 提交事务
        session.commit();
        // 关闭连接
        session.close();
    }

    @Test
    public void update(){
        // 获得连接
        SqlSession session = factory.openSession();
        // 数据库操作
        Product product = new Product();
        product.setPid(28);
        product.setTid(2);
        product.setPname("华为P40 pro");
        product.setPimage("bbbb");
        product.setPtime(new Date());
        product.setPprice(40.0);
        product.setPstate(1);
        product.setPinfo("非常好");
        ProductDAO productDAO = session.getMapper(ProductDAO.class);
        Assert.assertTrue(productDAO.update(product)==1);

        // 提交事务
        session.commit();
        // 关闭连接
        session.close();
    }

    @Test
    public void testFindAll(){
        // 获得连接
        SqlSession session = factory.openSession();
        // 数据库操作
        ProductDAO productDAO = session.getMapper(ProductDAO.class);
        Assert.assertTrue(productDAO.findAll().size() > 20);
        // 关闭连接
        session.close();
    }

    @Test
    public void testFindById(){
        // 获得连接
        SqlSession session = factory.openSession();
        // 数据库操作
        ProductDAO productDAO = session.getMapper(ProductDAO.class);
        Assert.assertNotNull(productDAO.findById(1));
        // 关闭连接
        session.close();
    }
}

经典面试题:#和$的区别:

  • #相当于占位符?,可以防止sql注入
  • #在传入参数时,需要对应对象的属性或者map的key,当只有一个参数时,可以随意
  • $相当于字符串拼接,有sql注入的风险
  • $在传入参数,只能对应对象的属性或map的key,即使只有一个参数,也需要传入对象或者map,可以用@Param注解自动封装map
  • $的使用场景:一般用在需要动态传入表名或列名等关键内容时。

五、关联查询[难点]

5.1 表之间的关系

一对一:项目中使用并不多,也可以整合成一张表。例如:用户基本信息和用户详情。两张表操作的频率不一致,所以拆分以提升性能。如果新建两个实体类,在任何一个实体类中添加另一个实体类的属性皆可。意味着可以在任何一张表添加对应另一张表的外键。

一对多(多对一):部门和员工是一对多的关系。班级和学生是一对多的关系。多对一是一对多的反向描述。员工和部门就是多对一的关系。在表设计时,需要在多方添加外键。在实体类设计时,在多方(员工)里面需要设置一方(部门)的对象,在一方(订单)可以设置多方(订单项)的集合。

多对多:学生和课程是多对多的关系。角色和权限是多对多的关系。在表设计上,需要新建中间表来保存两者的多对多关系。在实体类设计上,在任何一方都可以写一个对应的另一方的集合。

5.2 多对一查询(一对一)

此处使用产品和类型作为案例。

实体类封装为:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    private Integer pid;
    private String pname;
    private Date ptime;
    private String pimage;
    private Double pprice;
    private Integer pstate;
    private String pinfo;
    private Type type;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Type {
    private Integer tid;
    private String tname;
    private String tinfo;
}

映射文件编写:

ProductMapper.xml


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.day53.dao.ProductDAO">
    
    <sql id="productColumns">
        p_id, t_id, p_name, p_time, p_image, p_price, p_state, p_info
    sql>

    <resultMap id="productMap" type="com.qf.day53.entity.Product">
        
        
        <id property="pid" column="p_id">id>
        
        <result property="pname" column="p_name">result>
        <result property="ptime" column="p_time">result>
        <result property="pimage" column="p_image">result>
        <result property="pprice" column="p_price">result>
        <result property="pstate" column="p_state">result>
        <result property="pinfo" column="p_info">result>
        
        <association property="type" column="t_id" select="com.qf.day53.dao.TypeDAO.findById">association>
    resultMap>

    <select id="findAll" resultMap="productMap">
        SELECT
        <include refid="productColumns">include>
        FROM product
    select>
    <select id="findById" resultMap="productMap">
        SELECT
        <include refid="productColumns">include>
        FROM product
        WHERE p_id = #{id}
    select>
mapper>

TypeMapper.xml


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.day53.dao.TypeDAO">
    <sql id="typeColumns">
        t_id,t_name,t_info
    sql>
    <resultMap id="typeMap" type="com.qf.day53.entity.Type">
        <id property="tid" column="t_id">id>
        <result property="tname" column="t_name">result>
        <result property="tinfo" column="t_info">result>
    resultMap>

    <select id="findById" resultMap="typeMap">
        SELECT
        <include refid="typeColumns">include>
        FROM `type`
        WHERE t_id = #{id}
    select>
mapper>
public class ProductDAOTest {
    private SqlSessionFactory factory;

    @Before
    public void setUp() throws Exception {
        InputStream inputStream = Resources.getResourceAsStream("mybatisCfg.xml");
        factory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    @Test
    public void findAll() throws Exception {
        SqlSession session = factory.openSession();
        ProductDAO productDAO = session.getMapper(ProductDAO.class);
        System.out.println(productDAO.findAll());
        session.close();
    }

    @Test
    public void findById() throws Exception {
        SqlSession session = factory.openSession();
        ProductDAO productDAO = session.getMapper(ProductDAO.class);
        System.out.println(productDAO.findById(1));
        session.close();
    }
}
5.3 一对多查询(多对多)

以订单和订单项为案例。

与上面的多对一查询的关联类似,区别在于查询结果为一个集合。

OrderMapper.xml


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.day53.dao.OrderDAO">
    <sql id="orderColumns">
        o_id, u_id, a_id, o_count, o_time, o_state
    sql>
    <resultMap id="orderMap" type="com.qf.day53.entity.Orders">
        <id property="oid" column="o_id">id>
        <result property="uid" column="u_id">result>
        <result property="aid" column="a_id">result>
        <result property="ocount" column="o_count">result>
        <result property="otime" column="o_time">result>
        <result property="ostate" column="o_state">result>
        
        <collection property="items" column="o_id" select="com.qf.day53.dao.ItemDAO.findByOrderId">collection>
    resultMap>

    <select id="findById" resultMap="orderMap">
        SELECT
        <include refid="orderColumns">include>
        FROM `orders`
        WHERE o_id = #{id}
    select>
mapper>

ItemMapper.xml


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.day53.dao.ItemDAO">
    <sql id="itemColumns">
        i_id, o_id, p_id, i_count, i_num
    sql>
    <resultMap id="itemMap" type="com.qf.day53.entity.Item">
        <id property="iid" column="i_id">id>
        <result property="oid" column="o_id">result>
        <result property="icount" column="i_count">result>
        <result property="inum" column="i_num">result>
        <association property="product" column="p_id" select="com.qf.day53.dao.ProductDAO.findById">association>
    resultMap>

    <select id="findByOrderId" resultMap="itemMap">
        SELECT
        <include refid="itemColumns">include>
        FROM `item`
        WHERE o_id = #{id}
    select>
mapper>
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Orders {
    private String oid;
    private Integer uid;
    private Integer aid;
    private Double ocount;
    private Date otime;
    private Integer ostate;
    private List<Item> items;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Item {
    private Integer iid;
    private String oid;
    private Product product;
    private Double icount;
    private Integer inum;
}
5.4 查询过程中涉及的缓存

问题1:在查询商品时关联查询商品类型,发现有29条商品数据,只有2条类型数据,所以类型表只被查询了2次,而不是29次。

问题2:在不需要显示类型时也会查询类型表。

问题3:如果设置了lazy,当关闭连接后,循环打印类型信息时,每一次打印都会自动开启连接并查询,查询完毕后关闭连接。一共查询了29次类型。

答案:

1、在连接开启到连接关闭过程中,查询的所有数据都会被暂时的放到内存中缓存起来,如果在关闭连接之前,还需要使用该数据,会先在缓存中查询是否存在该数据,如果存在,则直接使用,而不需要去数据库查询。

2、因为默认为eager(渴望的),可以设置为lazy(懒加载,需要的时候才查询)。

5.5 ResultMap的继承

上面的订单和订单项案例中,如果查询订单详情,应该关联查询订单项,但是如果是查询所有的订单信息列表,不会显示订单项,此时不应该关联查询订单项。

可以编写两个resultMap,使用继承的方式来实现配置的复用。

<resultMap id="orderMap" type="com.qf.day53.entity.Orders">
    <id property="oid" column="o_id">id>
    <result property="uid" column="u_id">result>
    <result property="aid" column="a_id">result>
    <result property="ocount" column="o_count">result>
    <result property="otime" column="o_time">result>
    <result property="ostate" column="o_state">result>
resultMap>

<resultMap id="orderDetailMap" type="com.qf.day53.entity.Orders" extends="orderMap">
    
    <collection property="items" fetchType="eager" column="o_id" select="com.qf.day53.dao.ItemDAO.findByOrderId">collection>
resultMap>
5.6 级联添加

仅仅为了演示操作过程,使用一个不是很恰当的案例:

添加一个类型的同时,添加该类型的产品。

问题:

类型id是自增的,当添加完类型时,根本不知道id是多少,紧接着要添加的产品需要该类型id作为外键。

TypeMapper.xml


<insert id="save" useGeneratedKeys="true" keyProperty="tid">
    INSERT INTO `type`(t_name,t_info) VALUES (#{tname}, #{tinfo})
insert>
@Test
    public void save() throws Exception {
        SqlSession session = factory.openSession();
        TypeDAO typeDAO = session.getMapper(TypeDAO.class);
        // 创建Type对象并添加
        Type type = new Type();
        type.setTname("笔记本");
        type.setTinfo("好");
        typeDAO.save(type);

        ProductDAO productDAO = session.getMapper(ProductDAO.class);
        // 创建产品对象并添加
        Product product = new Product();
        // 此处使用的type对象的tid属性,但是上面创建过程中并没有设置,是通过在配置文件中配置的type的save方法中使用自增并回填的方式获取的tid属性。
        product.setType(type);
        product.setPname("华为笔记本");
        product.setPimage("bbbb");
        product.setPtime(new Date());
        product.setPprice(4000.0);
        product.setPstate(1);
        product.setPinfo("非常好");
        productDAO.save(product);

        session.commit();
        session.close();
    }

六、动态SQL[重点]

根据条件动态生成的sql语句。而不是一开始就写好的静态语句。

6.1 if

判断属性是否存在,如果存在则拼接相应的sql。

注意:在动态sql的标签中pname名称,必须是一个对象的属性名称或者是map对应的key


<select id="findAll" resultMap="productMap">
    SELECT
    <include refid="productColumns">include>
    FROM product
    WHERE 1 = 1
    <if test="pname != null">
        AND p_name LIKE #{pname}
    if>
select>
public interface ProductDAO {
    // 可以使用@Param注解,作用是将当前的参数封装到一个map中,以注解中的名称作为map的key
    List<Product> findAll(@Param("pname") String pname);
}
6.2 choose、when、otherwise

相当于条件分支,补充上面的if的不足。用法类似于jstl中的。

注意:条件分支只会执行一个。

<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>
6.3 where、set

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>

set:会自动去掉最后一个修改列后面的逗号

<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>
6.4 trim[难点]

trim是where、set的原生写法,也就是说可以处理相应的字符串,比如去掉前面的and,去掉后面的,号,还可以在前后添加一些内容。

where和set由于功能比较常用,所以单独写了标签。


<insert id="save">
    INSERT INTO product
    <trim suffixOverrides="," prefix="(" suffix=")VALUES (">
        <if test="type != null and type.tid != null">
            t_id,
        if>
        <if test="pname != null">
            p_name,
        if>
        <if test="ptime != null">
            p_time,
        if>
    trim>
    <trim suffixOverrides="," suffix=")">
        <if test="type != null and type.tid != null">
            #{type.tid},
        if>
        <if test="pname != null">
            #{pname},
        if>
        <if test="ptime != null">
            #{ptime},
        if>
    trim>
insert>
6.5 foreach

foreach循环拼接sql。

例如需要查询id为3,5,8的数据。

一般情况下sql可能会select … where id in (3, 5, 8)


<select id="findAllByIds" resultMap="productMap">
    SELECT
    <include refid="productColumns">include>
    FROM product
    WHERE
    p_id IN
    <foreach collection="list" open="(" close=")" item="o" separator=",">
        #{o}
    foreach>
select>
public interface ProductDAO {
    List<Product> findAllByIds(List<Integer> ids);
}

七、Mybatis中注解的使用

7.1 @Param

用来封装接口方法中多个参数,封装成map传入到sql操作中。参考上面6.1if使用。

User login(@Param("username") String username, @Param("password") String password);
<select id="login" resultType="com.qf.day54.entity.User">
    select * from `user` where username = #{username} and password = #{password}
select>
7.2 @select、@update、@delete、@insert等注解

代替mapper中对应的增删改查操作

public interface TypeDAO {
    @Select("select count(1) from `type`")
    Long count();

    @Insert({"insert into `type`(t_name, t_info) values (#{tname}, #{tinfo})"})
    int save(Type type);

    @Results(id = "typeMap",
            value = {
                    @Result(property = "tid", column = "t_id", id = true),
                    @Result(property = "tname", column = "t_name"),
                    @Result(property = "tinfo", column = "t_info"),
                    @Result(property = "productList", column = "t_id", many = @Many(select = "com.qf.day54.ProductDAO.findAllByTypeId", fetchType = FetchType.LAZY))
            }
    )
    @Select("select * from `type`")
    List<Type> findAll();

    @ResultMap("typeMap")
    @Select("select * from `type` where t_id = #{id}")
    Type findById(Integer tid);

    @UpdateProvider(type = TypeDAOProvider.class, method = "update")
    int update(Type type);
}

public class TypeDAOProvider {
    public String update(Type type){
        SQL sql = new SQL();
        sql.UPDATE("type");
        if (type.getTname() != null){
            sql.SET("t_name", "#{tname}");
        }
        if (type.getTinfo() != null){
            sql.SET("t_info", "#{tinfo}");
        }
        sql.WHERE("t_id", "#{tid}");
        return sql.toString();
    }
}

注意:如果是纯注解,那么在核心配置文件中需要使用class来对应。

mybatisCfg.xml


<mappers>
    <mapper resource="mapper/ProductMapper.xml">mapper>
    <mapper class="com.qf.day54.dao.TypeDAO">mapper>
mappers>

注意:可以映射文件和注解同时存在,但是不能冲突。在注解中可以引用映射文件中的内容,比如@ResultMap注解。

你可能感兴趣的:(Java程序设计,mybatis,java,数据库)