系列文章

SSM之MyBatis 01 —— 第一个MyBatis程序、增删改查(模糊查询)

SSM之MyBatis 02 —— 配置文件说明、日志工厂、分页(Limit和RowBounds)

SSM之MyBatis 03 —— 使用注解开发、Lombok、多对一&一对多处理

SSM之MyBatis 04 —— 动态SQL、缓存Cache



八、使用注解开发

8.1、面向接口编程

  • 我们都学过面向对象编程,也学过接口,但在真正的开发中,我们很多时候都是选择面向接口编程。
  • 根本原因:解耦、可拓展、提高复用、分层开发,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得更容易,规范性更好。
  • 在面向对象的系统中,系统的各种功能是由许多不同对象协作完成的,各个对象内部具体是如何实现的,这对于系统设计者来说不那么重要。
  • 而各个对象间的协作关系是系统设计的关键,不同类的通信、各模块间的交互,这些在系统设计之初就得考虑清楚,面向接口编程就是按照这种思想。

关于接口的理解

  • 接口是定义(规范、约束)与实现的分离
  • 接口本身反映了系统设计人员对系统的抽象理解
  • 接口分为两类:
    • 个体的抽象,对应于一个抽象体(abstract class)
    • 一个个体某一方面的抽象,形成一个抽象面(interface)
  • 一个个体可能有多个抽象面,抽象体和抽象面是有区别的

三个面向的区别

  • 面向对象:我们考虑问题时,以对象为单位,考虑它的属性和方法。
  • 面向过程:我们考虑问题时,以一个具体的流程为单位,考虑它的实现。
  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体系是对系统整体的架构。

8.2、注解开发

本质:反射机制

我另外有讲注解与反射的博客:Java 注解与反射 01 —— 注解Java 注解与反射 02 —— 反射

底层:动态代理(前面Java基础的多线程讲过,后面Spring也会详细讲)

1、注解在接口上实现

package com.zcy.dao;

import com.zcy.pojo.User;
import org.apache.ibatis.annotations.Select;
import java.util.List;

public interface UserMapper {
    @Select("select * from user")
    public List<User> getAllUser();
}

2、在核心配置文件中绑定接口

注意这里是用的class方式,因为用注解就不需要用mapper.xml,也就没用resource方式。

<mappers>
    <mapper class="com.zcy.dao.UserMapper"/>
</mappers>

3、测试

package dao;

import com.zcy.dao.UserMapper;
import com.zcy.pojo.User;
import com.zcy.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserMapperTest {
    @Test
    public void getAllUser(){
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        List<User> userList = userMapper.getAllUser();

        for (User user : userList)
            System.out.println(user);

        sqlSession.close();
    }
}

结果:

在这里插入图片描述

可以发现,由于实体类User的成员变量和数据库中user表的字段名不一致,导致username和pwd为空,而使用注解方式显然不能用resultMap来解决,官方也说明了注解方式可以让代码更加简介,但那是在比较简单的场景下,大多数复杂情况使用注解只会更加麻烦。

MyBatis详细执行流程(了解)

public class MyBatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    static {
        String resource = "mybatis-config.xml";

        try {
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

在MyBatisUtils工具类中,通过将文件mybatis-config.xml转为流,然后调用SqlSessionFactoryBuilder的build方法,而在build方法中会调用重载的build,将环境、配置文件等作为XMLConfigBuilder对象封装好,最后在Configuration对象里。

在这里插入图片描述

接着才是我们看见的,得到SqlSessionFactory对象,而在我们得到SqlSession对象前,还有事务以及执行器。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UyAQbbmk-1614038856245)(MyBatis2.assets/1613979082099.png)]
在这里插入图片描述

在这里插入图片描述

8.3、注解CRUD

关于@Param()注解

  • 基本类型的参数或者String类型,需要加上
  • 引用类型不需要加
  • 如果只有一个基本类型,可以忽略,但建议也加
  • 在SQL中引用的变量就是@Param中设定的属性名

1、编写接口,增加注解,注意基本类型的参数需要添加Param注解

package com.zcy.dao;

import com.zcy.pojo.User;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface UserMapper {
    @Select("select * from user")
    public List<User> getAllUser();

    //如果是基本类型的参数,前面需要用Param注解,注解里的值就是SQL中引用的变量
    @Delete("delete from user where id = #{uid}")
    public int deleteUser(@Param("uid") int id);

    @Insert("insert into user (id, name, password) values(#{id}, #{username}, #{pwd})")
    public int addUser(User user);
    
    @Update("update user set name=#{username}, password=#{pwd} where id=#{id}")
    public int updateUser(User user);
}

2、测试(之前已经注册过接口了)

@Test
public void updateUser(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    User user = new User(4, "小天", "10101010");
    userMapper.updateUser(user);

    sqlSession.close();
}

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fVAoahqj-1614038856250)(MyBatis2.assets/1613980400352.png)]

九、Lombok

当我们写POJO,即实体类的时候,常常会写大量毫无营养的代码,例如get、set、toString等等,而Lombok可以使我们通过注解方式跳过这种步骤。

优点:

  • 代码更加简洁,无需自己去写构造方法、get/set等,提高一定效率
  • 属性做出修改时,也简化了维护这些属性所生成的get/set方法等

缺点:

  • 不支持多种参数构造器的重载(当然,用了Lombok后依然可以自己写重载构造方法)
  • 降低了源代码可读性、完整性

1、IDEA安装Lombok插件

1613981514741

2、maven导入依赖(或者自己导入jar包)

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>

3、常用注解说明

  • @NonNull:写在方法的参数前面,自动对该参数进行非空校验,为空则抛出NullPointerException
  • @Getter and @Setter:用在属性上,这样无需自己写set和get方法,还能指定访问范围
  • @ToString:用在类上,可自动覆盖toString方法
  • @EqualsAndHashCode:用在类上,自动生成equals方法和hashCode方法
  • @NoArgsConstructor、@AllArgsConstructor:用在类上,自动生成无参/所有参数的构造方法
  • @Data:用在类上,相当于同时使用了@ToString、@EqualsAndHashCode、@Getter、@Setter、和@RequiredArgsConstructor,对实体类非常有用
  • @Value:用在类上,是@Data的不可变形式,相当于给属性添加final声明,只提供get方法而不提供set
  • @SneakyThrows:自动抛受检异常,而无需显示在方法上使用throws语句
  • @Synchronized:用在方法上,将方法声明为同步的,并自动加锁
  • 更多的请百度

4、使用示例,修改User类

如果用了@Data注解,就不会包含有参构造方法,而加上有参构造注解,就会覆盖无参注解,所以需要都写上,才能同时有有参和无参构造方法。

1613982625275

十、多对一处理

1613983314727
  • 关联:对于学生而言,多个学生关联一个老师(多对一)
  • 集合:对于老师而言,一个老师集合了很多学生(一对多)

数据库中的多对一,我们通常有两种方式:联表查询或者子查询。这里也是两种方式,如果我们仅仅直接查询学生或者老师,那么它们的属性类型是对象的值都会为null,这是由属性名和字段不一致造成的。因此解决方法都需要利用到结果集。

现在创建老师、学生表:

CREATE TABLE `teacher` (
                           `id` INT(10) NOT NULL,
                           `name` VARCHAR(30) DEFAULT NULL,
                           PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师');

CREATE TABLE `student` (
   `id` INT(10) NOT NULL,
   `name` VARCHAR(30) DEFAULT NULL,
   `tid` INT(10) DEFAULT NULL,
   PRIMARY KEY (`id`),
   KEY `fktid` (`tid`),
   CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');

在这里插入图片描述

10.1、测试环境搭建

新建一个模块,先搭建好基本的测试环境(db.properties、mybatis-config.xml)。

1613985768942

1、编写MyBatis工具类,并导入Lombok,方便后面简化实体类编写

2、新建实体类 Teacher、Student

//多对一:每个学生有一个老师
@Data
public class Student {
    private int id;
    private String name;
    //这里不用tid,这是Java 组合,用Teacher更好将学生和老师绑定
    private Teacher teacher;
}

//一对多:一个老师会有多个学生
@Data
public class Teacher {
   private int id;
   private String name;
   private List<Student> students;
}

3、新建接口

package com.zcy.dao;

import com.zcy.pojo.Student;

import java.util.List;

public interface StudentMapper {
    //按照查询嵌套处理
    public List<Student> getStudent1();
    //按照结果嵌套处理
    public List<Student> getStudent2();
}

4、新建StudentMapper.xml

5、通过mybatis-config.xml注册mapper

6、测试

10.2、按照查询嵌套处理

查出每个学生以及他们每人对应的老师,这就需要联表查询(查两个表),光靠以之前学的方法是无法完成的。

<?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.zcy.dao.StudentMapper">

    <!-- 思路:1. 先查初所有学生信息 2. 根据查出来的学生的tid,寻找对应的老师!这里利用了子查询 -->

    <!-- 查出所有学生,学生Student的结果集是StudentTeacher -->
    <select id="getStudent1" resultMap="StudentTeacher">
        select * from student
    </select>

    <!-- getStudent1的返回值最终是Student,这里用了嵌套子查询  -->
    <resultMap id="StudentTeacher" type="Student">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <!-- 复杂的属性需要单独处理,对象:association,集合:collection -->
        <!-- 因为teacher是对象,需要指定其对象类型,它对应数据库的tid字段,将tid作为getTeacher的变量  -->
        <association property="teacher" javaType="Teacher" column="tid" select="getTeacher"/>
    </resultMap>

    <!-- 根据结果集StudentTeacher传的参数 tid 查询老师-->
    <select id="getTeacher" resultType="Teacher">
        <!-- 这里不一定写成tid,因为只传一个参数,随便写一个,MyBatis会自动推断是tid -->
        select * from teacher where id = #{tid}
    </select>
</mapper>

10.3、按照结果嵌套处理

<!-- 方式二  -->
<!-- 按照正常的SQL写,同时指定结果集,因为返回类型肯定不是一个纯粹的Student -->
<select id="getStudent2" resultMap="StudentTeacher2">
    select s.id sid, s.name sname, t.name tname
    from student s, teacher t
    where s.tid = t.id
</select>

<!-- 结果集就按照Student类对应着写 Student的属性 id、name、teacher -->
<resultMap id="StudentTeacher2" type="Student">
    <!-- 基础类型就对应着写 id、name        -->
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <!-- 特殊类型就单独再写,teacher是一个对象,Teacher类型(如果是集合又会不一样)  -->
    <association property="teacher" javaType="Teacher">
        <!-- 根据数据库查询的返回结果,将t.name指定别名为tname,所以Teacher中column需要对应 -->
        <result property="name" column="tname"/>
    </association>
</resultMap>

十一、一对多处理

11.1、测试环境搭建

大部分和多对一的环境一样,实体类、MyBatis工具类等等…

1、接口编写

public interface TeacherMapper {
    //基本类型,指定参数。后面在TeacherMapper.xml中也就使用tid为参数
    public Teacher getTeacherById1(@Param("tid") int id);
    public Teacher getTeacherById2(@Param("tid") int id);
}

2、新建TeacherMapper.xml

3、通过mybatis-config.xml注册mapper

11.2、按照结果嵌套处理

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Maper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zcy.dao.TeacherMapper">
    <!-- 按结果嵌套查询    -->
    <select id="getTeacherById1" resultMap="TeacherStudent1">
        select t.id tid, t.name tname, s.id sid, s.name sname
        from teacher t, student s
        where t.id = s.tid
    </select>
    <resultMap id="TeacherStudent1" type="Teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
        <!-- 由于students是集合List<Student>,所以用collection,同时不用JavaType而是ofType,指定泛型的约束类型-->
        <collection property="students" ofType="Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
        </collection>
    </resultMap>
</mapper>

11.3、按查询嵌套处理

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Maper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zcy.dao.TeacherMapper">
    <!-- 按查询嵌套处理 -->
    <!-- 查出所有老师,将结果作为结果集给第二个查询。这里的tid是@Param指定的名称    -->
    <select id="getTeacherById2" resultMap="TeacherStudent2">
        select * from teacher where id = #{tid}
    </select>
    <!--如果用嵌套子查询,则需要指定javaType。因为是集合,所以用collection    -->
    <resultMap id="TeacherStudent2" type="Teacher">
        <collection property="students" javaType="ArrayList" ofType="Student" select="getTeacherById"/>
    </resultMap>
    <!-- 接收结果集TeacherStudent2传的参数 tid(随便命名,因为参数只有一个),并根据tid查询student    -->
    <select id="getTeacherById" resultType="Student">
        select * from student where tid = #{tid}
    </select>
</mapper>
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐