开发框架 MyBatis

  MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。   本节内容主要是参考 MyBatis3 及以后版本支持的情况做出的描述。

MyBatis 配置说明

  接下来以用 idea 开发的 Java 项目进行说明。

  在使用 Maven 工具来管理 jar 包情况下,可以在下载 unvdb 数据库后从数据库 Interface/JDBC 路径拉取 unvdb 的 JDBC 驱动 jar 包,通过 mvn install:install-file -Dgroupld=com.unvdb -Dartifactld=unvdb-jdbc -Dversion=42.7.3 -Dpackaging=jar -Dfile=unvdb-jdbc-42.7.3.jar 将 JDBC 驱动导入到本地的 Maven 仓库。

  a. 在创建一个 Spring Boot 的项目之后在 pom.xml 文件中添加 Spring Boot 和 MyBatis 的依赖。

对于 Maven,添加以下依赖:

<!-- MyBatis Spring Boot Starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>
<!-- JUnit 测试依赖 -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <scope>test</scope>
</dependency>
<!-- 数据库连接驱动 -->
<dependency>
    <groupId>com.unvdb</groupId>
    <artifactId>unvdb-jdbc</artifactId>
    <version>42.7.3</version>
</dependency>
<!-- 配置国密所需依赖 -->
<dependency>
    <groupId>org.openeuler</groupId>
    <artifactId>bgmprovider</artifactId>
    <version>1.1.3</version>
</dependency>

  b.需要在java 包和resources 包下各创建一个文件夹,来存放mapper接口和mapper.xml文件。

  c.在resources目录下新建application.properties文件,用于存放数据库连接需要的一些配置数据(一般在新建Springboot项目都会自动生成该文件),也可以新建application.yml文件,不过格式得转换。以下是yml文件的相关配置:

datasource:
  driver-class-name: com.unvdb.Driver
  url: jdbc:unvdb://localhost:5678/test
  username: 登录名
  password: 登录密码

# MyBatis 配置
mybatis:
  config-location: classpath:/mybatis/mybatis-config.xml
  mapper-locations: classpath:/mybatis/mapper/*.xml # XML 文件存放路径

  d.需要在启动类中加入一个注解指定 mapper 接口的位置:

@SpringBootApplication
//MapperScan 在 springboot 启动的时后自动扫描 mapper,并根据 xml 自动生成对应的实现
@MapperScan("com.zoe.mybatis.mapper")
public class MybatisApplication {

    public static void main(String[] args) {
        SpringApplication.run(MybatisApplication.class, args);
    }
}

  至此,MyBatis 的适配属性文件以及映射文件工作已经完成了,接下来是来完成 Java 中对应的接口实例的开发工作。

MyBatis接口和实例开发示例

 在项目中写出数据库相应的数据访问层以及实体类,以示例项目为例:

a. 数据访问层:

import java.util.Date;

public class Emp {
    private Integer empno;
    private String ename;
    private String job;
    private Integer mgr;
    private Date hiredate;
    private Float sal;
    private Float comm;
    private Integer deptno;

    public Integer getEmpno() {
        return empno;
    }
    public void setEmpno(Integer empno) {
        this.empno = empno;
    }
    public String getEname() {
        return ename;
    }
    public void setEname(String ename) {
        this.ename = ename;
    }
    public String getJob() {
        return job;
    }
    public void setJob(String job) {
        this.job = job;
    }
    public Integer getMgr() {
        return mgr;
    }
    public void setMgr(Integer mgr) {
        this.mgr = mgr;
    }
    public Date getHiredate() {
        return hiredate;
    }
    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }
    public Float getSal() {
        return sal;
    }
    public void setSal(Float sal) {
        this.sal = sal;
    }
    public Float getComm() {
        return comm;
    }
    public void setComm(Float comm) {
        this.comm = comm;
    }
    public Integer getDeptno() {
        return deptno;
    }
    public void setDeptno(Integer deptno) {
        this.deptno = deptno;
    }
    public Emp() {
    }

    public Emp(Integer empno, String ename, String job, Integer mgr, Date hiredate, Float sal,
               Float comm, Integer deptno) {
        this.empno = empno;
        this.ename = ename;
        this.job = job;
        this.mgr = mgr;
        this.hiredate = hiredate;
        this.sal = sal;
        this.comm = comm;
        this.deptno = deptno;
    }
}

实体类:

public class Dept {
    private Integer deptno;

    private String dname;

    private String location;
}

b Mapper层:

  Mapper 层是 DAO 层的实现,负责编写与 DAO 接口对应的映射规则,即 SQL 和实体类(POJO)的映射规则。使用 MyBatis 等框架编写映射规则,将 SQL 语句与实体类关联起来。示例代码如下:

public interface EmpMapper {
    public Emp findById(Integer empno);

    //使用 List<Map>返回 list 数据
    public List<Map> findDepts();

    //使用 map 对象进行多参数返回
    public List<Map> findByDepts(Map param);

    //新增
    public void creates(Emp emp);

    //编辑
    public void updates(Emp emp);

    //删除
    public void deletes(Integer empno);
}

c.Service层:

  Service 层是业务逻辑层,处理具体的业务逻辑。创建一个与实体类对应的 Service 接口,定义业务方法。 示例代码如下:

@Service
public class EmpService {
    @Resource
    EmpMapper empMapper = null;
    public Emp findById(Integer empno){
        Emp emp = empMapper.findById(empno);
        return emp;
    }
    public List<Map> findDepts(){
        List<Map> list = empMapper.findDepts();
        return list;
    }
    public List<Map> findByDepts(Map param){
        List<Map> list = empMapper.findByDepts(param);
        return list;
    }
    // 开启事务的方法
    @Transactional(rollbackFor = Exception.class)
    public void create(Emp emp){
        empMapper.creates(emp);
    }
    // 开启事务的方法
    @Transactional(rollbackFor = Exception.class)
    public void update(Emp emp){
        empMapper.updates(emp);
    }
    // 开启事务的方法
    @Transactional(rollbackFor = Exception.class)
    public void delete(Integer empno){
        empMapper.deletes(empno);
    }
}

d.Controller层:

  Controller 层负责处理用户请求和返回响应。创建一个与实体类对应的 Controller 类,使用@RestController@RequestMapping 注解来定义 URL 映射,通过依赖注入方式引入 Service 接口,并在方法中调用 Service 层的方法来处理用户请求和返回响应。

示例代码如下:

@RestController
public class EmpController {
    @Autowired
    private EmpServiceImpl empService = null;
    
    @RequestMapping("/emp/{id}")
    public Emp findById(@PathVariable("id") Integer id){
        return empService.findById(id);
    }
    
    @RequestMapping("/emp/list")
    public List<Map> findDepts(){
        List<Map> list = empService.findDepts();
        return list;
    }
    
    @RequestMapping("/emp/findBydepts")
    public List<Map> findByDepts(String pdname, Float sal){
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("pdname", pdname);
        map.put("psal", sal);
        List<Map> list = empService.findByDepts(map);
        return list;
    }
    
    @RequestMapping("/emp/add")
    public Emp create(){
        Emp emp = new Emp();
        emp.setEname("zoe1");
        emp.setJob("sal");
        emp.setSal(4000f);
        emp.setHiredate(new Date());
        emp.setComm(20000f);
        // emp.setMgr(null);
        empService.create(emp);
        return emp;
    }
    
    @RequestMapping("/emp/update")
    public Emp update(){
        Emp emp = empService.findById(1);
        emp.setSal(1400f);
        empService.update(emp);
        return emp;
    }
    
    @RequestMapping("/emp/del")
    public String delete(Integer empno){
        empService.delete(empno);
        return "success";
    }
}

存储过程的使用

  为了和数据库进行交互,通常的做法是将SQL语句写在Java代码中,SQL语句和Java代码耦合在一起不利于后期维护修改,而MyBatis能够帮助我们将SQL语句和Java代码分离,方便了后期因需求变动而对SQL语句进行修改。SQL语句则存放于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">
<!-- 映射文件接口 namespace 指向接口 -->
<mapper namespace="com.zoe.mybatis.mapper.EmpMapper">
    <!-- id 指向方法名要一致 -->
    <select id="findById" parameterType="Integer" resultType="com.zoe.mybatis.entity.Emp">
        select * from emp where empno = #{value}
    </select>

    <!-- resultType="java.util.Map"
    将查询到的每一条记录保存成键值对的形式 key 字段名 value 值
    利用 map 有很好的可扩展性
    LinkedHashMap 按数据插入顺序返回 -->
    <select id="findDepts" resultType="java.util.LinkedHashMap">
        select * from emp e, dept d where e.deptno = d.deptno and dname = 'sal' and sal > 1000
    </select>

    <select id="findByDepts" parameterType="java.util.Map" resultType="java.util.LinkedHashMap">
        select * from emp e, dept d where e.deptno = d.deptno
        <if test="pdname != null">
            and dname = #{pdname}
        </if>
        <if test="psal != null">
            and sal > #{psal}
        </if>
    </select>

    <!-- selectKey 将 id 回填 keyProperty 主键名 keyColumn=属性名 LAST_INSERT_ID()
    mysql 内置方法 -->
    <insert id="creates" parameterType="com.zoe.mybatis.entity.Emp" useGeneratedKeys="true" keyProperty="empno">
        INSERT INTO "public"."emp"("comm", "ename", "hiredate", "job", "mgr", "sal", "deptno")
        VALUES (#{comm}, #{ename}, #{hiredate}, #{job}, #{mgr}, #{sal}, #{deptno});
        <!-- <selectKey keyProperty="empno" keyColumn="empno" resultType="Integer" order="AFTER">
        SELECT LAST_INSERT_ID()
        </selectKey>-->
    </insert>
    <update id="updates" parameterType="com.zoe.mybatis.entity.Emp">
        UPDATE "public"."emp" SET "comm" = #{comm}, "ename" = #{ename}, "hiredate" = #{hiredate}, "job" = #{job}, "mgr" = #{mgr}, "sal" = #{sal}, "deptno" = #{deptno} WHERE "empno" = #{empno};
    </update>

    <delete id="deletes" parameterType="Integer">
        delete from emp where empno = #{empno}
    </delete>
</mapper>

创建表的SQL语句

DROP SEQUENCE IF EXISTS "public"."dept_deptno_seq";
CREATE SEQUENCE "public"."dept_deptno_seq"
    INCREMENT 1
    MINVALUE 1
    MAXVALUE 9223372036854775807
    START 1
    CACHE 1;

DROP SEQUENCE IF EXISTS "public"."emp_empno_seq";
CREATE SEQUENCE "public"."emp_empno_seq"
    INCREMENT 1
    MINVALUE 1
    MAXVALUE 9223372036854775807
    START 1
    CACHE 1;

DROP TABLE IF EXISTS "public"."dept";
CREATE TABLE "public"."dept" (
    "deptno" int4 NOT NULL DEFAULT nextval('dept_deptno_seq'::regclass),
    "dname" varchar(255) COLLATE "pg_catalog"."default",
    "loc" varchar(255) COLLATE "pg_catalog"."default"
);

INSERT INTO "public"."dept" VALUES (1, 'manager', '1');
INSERT INTO "public"."dept" VALUES (2, 'sal', '2');

DROP TABLE IF EXISTS "public"."emp";
CREATE TABLE "public"."emp" (
    "empno" int4 NOT NULL DEFAULT nextval('emp_empno_seq'::regclass),
    "comm" float4,
    "ename" varchar(255) COLLATE "pg_catalog"."default",
    "hiredate" timestamp(6),
    "job" varchar(255) COLLATE "pg_catalog"."default",
    "mgr" int4,
    "sal" float4,
    "deptno" int4
);

INSERT INTO "public"."emp" VALUES (1, 2300, 'zoe', '2020-08-28 18:05:04', 'manager', NULL, 30000, 1);
INSERT INTO "public"."emp" VALUES (2, 5322, 'nelson', '2020-08-28 18:05:32', 'sal', NULL, 3000, 2);
INSERT INTO "public"."emp" VALUES (5, 20000, 'zoe1', '2020-08-31 17:08:54.235', 'sal', NULL, 4000, NULL);
INSERT INTO "public"."emp" VALUES (6, 20000, 'zoe1', '2020-08-31 17:09:19.469', 'sal', NULL, 4000, NULL);
INSERT INTO "public"."emp" VALUES (7, 20000, 'zoe1', '2020-08-31 17:09:19.995', 'sal', NULL, 4000, NULL);
INSERT INTO "public"."emp" VALUES (8, 20000, 'zoe1', '2020-08-31 17:09:20.227', 'sal', NULL, 4000, NULL);
INSERT INTO "public"."emp" VALUES (9, 20000, 'zoe1', '2020-08-31 17:09:20.442', 'sal', NULL, 4000, NULL);

ALTER SEQUENCE "public"."dept_deptno_seq" OWNED BY "public"."dept"."deptno";
SELECT setval('"public"."dept_deptno_seq"', 3, true);

ALTER SEQUENCE "public"."emp_empno_seq" OWNED BY "public"."emp"."empno";
SELECT setval('"public"."emp_empno_seq"', 10, true);

ALTER TABLE "public"."dept" ADD CONSTRAINT "dept_pkey" PRIMARY KEY ("deptno");

ALTER TABLE "public"."emp" ADD CONSTRAINT "emp_pkey" PRIMARY KEY ("empno");