深入浅出Mybatis(一)快速入门,复杂映射查询,动态sql

410 阅读10分钟

Mybatis简介

Mybatis是一款优秀的基于ORM的半自动轻量级持久化框架,它支持定制化SQL、存储过程以及高级映射。避免了几乎所有的JDBC代码和手动设置参数以及获取结果集,可以使用简单的XML或注解来配置和映射原生类型、接口和JavaPOJOPlain Old Java Objects,普通老式Java对象)为数据库中的记录。

ORM(对象/关系数据库映射)

全称Object/RelationMapping,完成面向对象的编程语言到关系数据库的映射。当ORM框架完成映射后,程序员既可以利用面向对象程序设计语言的简单易用性,又可以利用关系数据库的技术优势。ORM把关系数据库包装成面向对象的模型。ORM框架是面向对象设计语言与关系数据库发展不同步时的中间解决方案。采用ORM框架后,应用程序不再直接访问底层数据库,而是以面向对象的放松来操作持久化对象,而ORM框架则将这些面向对象的操作转换成底层SQL操作。ORM框架实现的效果:把对持久化对象的保存、修改、删除 等操作,转换为对数据库的操作

Mybatis快速入门

mybatis开发有注解开发和xml开发两种模式,这里先介绍xml开发,再介绍注解开发。

XML

1.新建maven项目,在pom.xml中添加相关依赖

<!--mybatis依赖-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.5</version>
</dependency>
<!--单元测试-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
<!--日志-->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.12</version>
</dependency>
<!--mysql驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.6</version>
    <scope>runtime</scope>
</dependency>
  1. 数据库表准备(user
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `password` varchar(50) DEFAULT NULL,
  `birthday` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
  1. 创建实体类User.java
public class User implements {
    private Integer id;
    private String username;
    private String password;
    private String birthday;
    //省略getter,setter方法
}
  1. 创建mapper接口UserMapper.java
public interface UserMapper {
    List<User> findAll();
    void insert(User user);
    void update(User user);
    void deleteById(int id);
}
  1. 创建UserMapper.xml映射文件
<?xml version="1.0" encoding="UTF-8" ?>

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

<mapper namespace="com.lagou.dao.UserMapper">

    <select id="findAll" resultType="com.lagou.domain.User">
        select * from User
    </select>
    
</mapper>

注意:默认情况下映射文件的位置在resources目录下,并且要与接口文件的包名路径保持一致, namespace必须与当前映射文件对应mapper接口的全限定类名,sql语句的id属性必须与接口文件中的方法名一一对应。

在这里插入图片描述

映射文件结构介绍:

在这里插入图片描述 6. 在resources目录下创建核心文件SqlMapConfig.xml

<!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">
        <!-- 当前事务交由JDBC管理-->
        <transactionManager type="JDBC"/>
        <!--  使用mybais提供的连接池   -->
        <dataSource type="POOLED">
            <!-- 数据库驱动 -->
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <!-- jdbcUrl -->
            <property name="url" value="jdbc:mysql:///test"/>
            <!-- 用户名 -->
            <property name="username" value="root"/>
            <!-- 密码 -->
            <property name="password" value="root"/>
        </dataSource>
        </environment>
    </environments>
    <!--  引入配置文件  -->
    <mappers>
        <mapper resource="com/lagou/mapper/UserMapper.xml"/>
    </mappers>
</configuration>
  1. 创建测试类UserTest.java
public class UserTest {
    private UserMapper userMapper;
    @Before
    public void before() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        userMapper = sqlSession.getMapper(UserMapper.class);
    }
    @Test
    public void test() {
        List<User> all = userMapper.findAll();
        for (User user : all) {
            System.out.println(user);
        }
    }
}
  1. 执行结果 在这里插入图片描述

注解版

注解开发流程与xml版本开发流程大致相同,只是不需要再创建UserMapper.xml映射文件了,SQL语句直接通过注解的方式写在接口UserMapper.java中,主要用到@Select,@Insert,@Update,@Delete注解,与XML映射文件里边的标签对应。

public interface UserMapper {

   @Insert("insert into user (username, password, birthday) values(#{username},#{password},#{birthday})")
    void insertByAnnotation(User user);

    @Update("update user set username = #{username} where id = #{id}")
    void updateByAnnotation(User user);

    @Select("select * from user")
    List<User> findAllByAnnotation();

    @Delete("delete from user where id = #{id}")
    void deleteByAnnotation(Integer id);
}

复杂映射查询

实际开发中,我们经常遇到关联查询的情况,主要分为一对一,一对多,多对多三种情况,下面就针对这三种情况分别介绍,还是按照XML和注解两种方式来介绍。

数据库准备

订单表orders):用户与订单表是一对多的关系,一个用户可以有多个订单,但是一个订单只能属于一个用户,订单与用户为一对一关系。

CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `ordertime` varchar(255) DEFAULT NULL,
  `total` double DEFAULT NULL,
  `uid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `uid` (`uid`),
  CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

角色表sys_role):用户与角色为多对多关系,一个用户可以有多个角色,一个角色可以属于多个用户

CREATE TABLE `sys_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `rolename` varchar(255) DEFAULT NULL,
  `roleDesc` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

用户角色关联表sys_user_role):维护用户与角色之间的多对多关联关系

CREATE TABLE `sys_user_role` (
  `userid` int(11) NOT NULL,
  `roleid` int(11) NOT NULL,
  PRIMARY KEY (`userid`,`roleid`),
  KEY `roleid` (`roleid`),
  CONSTRAINT `sys_user_role_ibfk_1` FOREIGN KEY (`userid`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `sys_user_role_ibfk_2` FOREIGN KEY (`roleid`) REFERENCES `sys_role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

XML版

一对一查询

需求:查询订单时,连带查询出改订单所对应的的用户信息

  1. 创建Order.java实体类,类中维护一个User变量
public class Order {
    private Integer id;
    private String orderTime;
    private Double total;
    private User user;
    //省略getter,setter方法
}
  1. 创建OrderMapper.java接口
public interface OrderMapper {
    List<Order> findOrderAndUser();
}
  1. 创建OrderMapper.xml映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lagou.dao.OrderMapper">
    <resultMap id="orderMap" type="com.lagou.pojo.Order">
        <result property="id" column="id"></result>
        <result property="orderTime" column="ordertime"></result>
        <result property="total" column="total"></result>
        <association property="user" javaType="com.lagou.pojo.User">
            <result property="id" column="uid"></result>
            <result property="username" column="username"></result>
        </association>
    </resultMap>
    <select id="findOrderAndUser" resultMap="orderMap">
        select * from orders o,user u where o.uid = u.id
    </select>
</mapper>

主要需要注意的是我们自定义了resultMap标签用来封装查询的结果集,将数据库字段和实体类属性一一对应,并且对于实体类中的一对一复杂类型属性使用association标签声明映射关系以及对应的实体类。

  1. 创建测试类OrderTest.java
public class OrderTest {

    private OrderMapper orderMapper;

    @Before
    public void before() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        orderMapper = sqlSession.getMapper(OrderMapper.class);
    }

    @Test
    public void test() {
        List<Order> orders = orderMapper.findOrderAndUser();
        for (Order order : orders) {
            System.out.println(order);
        }
    }
}
  1. 运行结果

在这里插入图片描述

一对多查询

查询用户时,连带查询出该用户所有的订单信息

  1. 在之前步骤创建的User.java实体类中新增属性orders,因为是一对多,所以用List
private List<Order> orders;
//省略getter,setter方法
  1. UserMapper.java接口定义新方法
List<User> findUserAndOrder();
  1. UserMapper.xml映射文件中新增对应的SQL语句
<resultMap id="userMapper" type="com.lagou.pojo.User">
    <id property="id" column="id"></id>
    <result property="username" column="username"></result>
    <result property="password" column="password"></result>
    <result property="birthday" column="birthday"></result>
    <collection property="orders" ofType="com.lagou.pojo.Order">
        <id property="id" column="oid"></id>
        <result property="orderTime" column="ordertimr"></result>
        <result property="total" column="total"></result>
    </collection>
</resultMap>

<select id="findUserAndOrder" resultMap="userMapper">
    select u.*, o.id as oid, o.ordertime,o.total,o.uid from user u left join orders o on o.uid = u.id
</select>

一对多和一对一一样需要维护一个resultMap返回值类型,但需要注意的是在配置一对多映射关系的时候使用collection标签,然后就是在SQL关联查询的时候如果遇到不同表中相同名称的字段,需要起别名以作区分。

  1. UserTest.java类中新增测试方法
@Test
public void test9() {
    List<User> list = userMapper.findUserAndOrder();
    for (User user : list) {
        System.out.println(user);
    }
}
  1. 结果

在这里插入图片描述

多对多查询

多对多查询步骤和一对多查询完全一致,只是查询的sql不一样,这里就不做赘述了。

注解版

注解版查询与xml查询区别就是不需要编写映射文件,而是将查询语句利用注解写在mapper接口中,所以我们只需要在mapper接口中做修改就可以了。

一对一查询

需求:查询订单时,连带查询出改订单所对应的的用户信息

OrderMapper.java接口中新增基于注解的查询方法

@Results({
    @Result(property = "id", column = "id"),
    @Result(property = "orderTime", column = "ordertime"),
    @Result(property = "total", column = "total"),
    @Result(property = "user", column = "uid",javaType = User.class, one=@One(select = "com.lagou.dao.UserMapper.findById")),
})
@Select("select * from orders")
List<Order> findOrderAndUserByAnnotation();

可以看出我们使用了@Results,@Result,@One三个新注解,前边两个注解很好理解,和xml 方式里边的resultMap标签的效果一样。这里主要介绍下边的代码,也是一对一注解查询的核心代码

@Result(property = "user", column = "uid",javaType = User.class, one=@One(select = "com.lagou.dao.UserMapper.findById"))

下边一一介绍里边的各个属性

  • property:对应实体类中的属性名,必须与属性名保持一致
  • javaType:声明该复杂类型属性的class
  • one:因为是一对一查询,所以用one
  • @One(select ="com.lagou.dao.UserMapper.findById"):这句话的作用是去执行com.lagou.dao.UserMapper.findById这个方法,根据userid查询用户对象,然后将结果关联到user属性中,这里很容易看出我们需要在UserMapper.java接口中再定义一个方法findById(),并且该查询语句需要一个参数userid
  • column:这个属性的值就是给上边的@One查询语句准备的传入参数userid,这里的值为uid,意思是将数据库查询出来的uid列名作为参数

经过上边的介绍我们知道还需要在UserMapper.java接口中再定义一个方法findById()

@Select("select * from user where id = #{id}")
User findById(Integer id);

一对多查询

查询用户时,连带查询出该用户所有的订单信息

UserMapper.java接口中新增基于注解的一对多查询方法

@Select("select * from user")
@Results({
        @Result(property = "id", column = "id"),
        @Result(property = "username", column = "username"),
        @Result(property = "password", column = "password"),
        @Result(property = "birthday", column = "birthday"),
        @Result(property = "orders", column = "id", javaType = List.class, many = @Many(select = "com.lagou.dao.OrderMapper.findByUid"))
})
List<User> findUserAndOrderByAnnotation();

与一对一不用的是这里我们使用了many = @Many(select = "com.lagou.dao.OrderMapper.findByUid"),一对多使用many@Many关键字,同样需要另外指定sql语句,以及传入参数所以我们还需要在OrderMapper.java中定义findByUid()方法

@Select("select * from orders where uid = #{uid}")
List<Order> findByUid(Integer uid);

多对多查询

多对多查询步骤和一对多查询完全一致,只是查询的sql不一样,这里就不做赘述了。

注解开发常用注解回顾

  • @Insert: 实现新增
  • @Update: 实现更新
  • @Delete: 实现删除
  • @Select: 实现查询
  • @Result: 实现结果集封装
  • @Results: 可以与@Result 一起使用,封装多个结果集
  • @One: 实现一对一结果集封装
  • @Many: 实现一对多结果集封装

动态SQL

动态SQL主要是让我们能够灵活的拼接SQL语句,进行一些逻辑判断,或者是复杂的操作,比如批量删除等,这里再补充下Mybatis中的常用标签

在这里插入图片描述 有些标签我们已经在上边内容中介绍过了,这里我们就主要介绍动态sql和格式化输出里边的标签的用法。

sqlinclude标签

这两个标签一般是成对出现的,sql用来定义一段可复用的查询代码,include用来在需要的地方引入,下面是他们的简单用法。

<sql id="selectUser">select * from user</sql>
<select id="findAll" resultType="User">
    <include refid="selectUser"></include>
</select>

if标签和

改标签主要用来执行逻辑判断,当满足条件时才执行

<select id="findBySelective" parameterType="user" resultType="user">
    select * from user where
    <if test="id != null">id = #{id}</if>
    <if test="username != null">and username = #{username}</if>
</select>

看到上边的语句,就会发现一个问题,当id为null而username不为null时,就会出现 where and 这样的语法错误,这种情况怎么解决呢,此时就需要用到where标签

where标签

where标签会知道如果它包含的标签中有返回值的话,它就插入一个where。此外,如果标签返回的内容是以 ANDOR 开头的,则它会剔除掉。

<select id="findBySelective" parameterType="user" resultType="user">
    select * from user
    <where>
        <if test="id != null">and id = #{id}</if>
        <if test="username != null">and username = #{username}</if>
    </where>
</select>

foreach标签

该标签主要用于构建 in 条件,可在 sql 中对集合进行遍历,常用到批量删除、添加等处理中

<select id="findByIds" parameterType="list" resultType="user">
    <include refid="selectUser"></include>
    <where>
        <foreach collection="array" open="id in (" close=")" item="id" separator=",">#{id}</foreach>
    </where>
</select>

主要属性介绍

  • collection: 该属性的值有三个分别是 listarraymap 三种,分别对应的参数类型为:List、数组、map 集合
  • item: 表示在遍历过程中每一个元素的别名
  • index: 表示在遍历过程中的下标,用不到可以不写
  • open: 拼接前缀
  • clsoe: 拼接后缀
  • separator: 每个元素之间的分隔符 在UserMapper.java接口中这样定义,参数是一个数组
List<User> findByIds(int[] ids);

choose标签

按顺序判断 when 中的条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when 的条件都不满足时,则执行 otherwise 中的 sql。类似于 Javaswitch 语句,chooseswitchwhencaseotherwise 则为 default

<select id="findBySelective" parameterType="user" resultType="user">
    select * from user
    <where>
        <choose>
            <when test="id != null">and id = #{id}</when>
            <when test="username != null">and username = #{username}</when>
            <otherwise>and id = 1</otherwise>
        </choose>
    </where>
</select>

set标签

where标签一样,set标签也是规范格式的标签主要配合if标签使用,当我们在 update 语句中使用 if 标签时,如果最后的 if 没有执行,则或导致逗号多余错误。此时使用 set 标签可以将动态的配置 set 关键字,和剔除追加到条件末尾的任何不相关的逗号。

<update id="update" parameterType="User">
    update user
     <set>
         <if test="username != null">username = #{username},</if>
         <if test="password != null">password = #{password}</if>
     </set>
    where id = #{id}
</update>

trim标签

也是一个规范格式的标签,主要属性有四个:

  • prefix: 在trim标签内sql语句加上前缀
  • suffix: 在trim标签内sql语句加上后缀
  • prefixOverrides: 指定去除多余的前缀内容,如:prefixOverrides='AND | OR',去除trim标签内sql语句多余的前缀"and"或者"or"
  • suffixOverrides:指定去除多余的前缀内容

下面写给出样例,一看就会

<update id="update" parameterType="User">
    update user
    <trim prefix="set" suffixOverrides=",">
        <if test="username != null">username = #{username},</if>
        <if test="password != null">password = #{password},</if>
    </trim>
    where id = #{id}
</update>

可以说它就是setwhere标签的结合版,可以替代他们的功能。

说明

文章内容输出来源:拉勾教育Java高薪训练营课程归纳总结