IDEA07:Mybatis和Springboot操作数据库

写在前面

这里介绍如何使用Mybatis操作数据库。

  • Mybatis是一种操作数据库的框架,比JDBC要方便。
  • Mybatis通常会和Spring结合使用。借助Spring的依赖注入/控制反转,数据库的相关操作会更加简洁高效。
  • Springboot是Spring及其相关组件的集大成者,可以高效地配置Spring框架,避免繁琐的配置过程。
  • 有关Spring的介绍可以看:Spring和SpringMVC总结篇spring 与 springmvc 的区别和定义
  • 详细的介绍可以看:Spring 常见面试题总结

一、SpringBoot框架层次

因为Mybatis最好还是和Spring框架结合来使用,作为Spring的持久层来完成数据库操作,所以这里首先介绍一下Springboot的基本层次。

  • Entity/POJO层:实体层。定义数据表对象,是最基本的类。类一般需要提供和数据表属性对应的成员变量,以及对应的GET和SET方法。
  • Dao/Mapper层:持久层。创建和数据库交互的接口,SQL的操作依赖于这一层。通常是在xxxMapper.xml中写好SQL语句,然后在这一层绑定接口,后续可以通过这一层的接口调用数据库操作。
  • Service层:业务层。可以在持久层上面继续加功能复杂化,完成某种数据库操作的完整功能。但它仍然是专注于单独一种数据库操作,如更新或者查询等,通常不会调用多个持久层接口。
  • Controller层:控制层。实现具体的业务逻辑,不只是涉及数据库操作,还有视图控制和具体业务逻辑实现。这一层通常是调用Service层接口实现数据库操作。

Springboot(或者说是SpringMVC)相当于使用了实体层、持久层和业务层三层来完成数据库操作。控制层相当于普通JAVA程序的main或者实现某种特定功能的函数。

二、使用Springboot框架

1.导入Maven

<parent>
   <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.2</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
    <!--spring boot dependencies-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.2</version>
    </dependency>

</dependencies>
  • 最基础的配置是一个<parent>和两个依赖;
  • <parent>意味着本项目不能使用它所代表的类,只能使用它所依赖的类,也就是中的类;
  • <dependencies>表明本项目既可以使用它所代表的类,也可以使用它所依赖的类;
  • 其余的依赖可以参考博客:spring boot的maven配置依赖详解

2.项目文件目录结构

项目文件目录结构

  • main文件夹用来写正式的类,test文件夹用来写测试的类;
  • test文件夹中的类能够调用main中定义的类,但main中的类不能调用test中定义的类;
  • 最好是定义一个有含义的包名,一般是类似于倒写的域名,如xxx.xxx.xxx格式,而不要直接在java文件夹下创建类;
  • 包名类似于C++中的命名空间,可以分开不同功能和目的的同名函数,方便管理;
  • 按照Springboot框架的层次,定义四个包;
  • 增加一个util包用来写自定义的功能性组件;
  • Springboot程序入口是SpringbootMain类,配置文件是application.yml
  • resources下的mapper是用来保存真正的数据库映射xml文件的,每个xml和持久层的xxxMapper类一一对应。

三、Springboot配置文件

server:
  port: 8081

mybatis:
  type-aliases-package: cn.jeremy.xxx.entity
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: false
    
spring:
  datasource:
    url: jdbc:sqlserver://xxx:1433;DatabaseName=xxx;encrypt=true;trustServerCertificate=true
    driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
    username: xxx
    password: xxx
  • application.yml中进行Springboot、database和mybatis的配置;
  • 当然也可不用application.yml而用application.properties来配置,但yml写起来更省事一点,两者其实也很容易相互转换;
  • type-aliases-package配置是要写上所有需要使用Mybaitis管理的实体类(也就是数据表对应的对象)所在包,多个包可用逗号分隔;
  • mapper-locations用来写xxxMapper.xml的路径,classpath就是指resources文件夹下的根路径;
  • map-underscore-to-camel-case用来进行数据库表属性和实体类的成员变量之间的自动映射,如果关闭了需要手动进行映射;
  • 注意:Springboot默认使用8080端口,这会和Zookeeper默认端口以及其他的Web应用冲突,所以最好是换一个端口,如8081。

四、持久层的Mybatis实现

前面已经提到了,Mybatis是Springboot的持久层框架,所以Springboot的持久层这里是采用Mybatis来实现(虽然持久层也可以用别的框架来实现,不局限于Mybatis)。整个Springboot框架的完整示例可以参考:Spring Boot 集成 MyBatis和 SQL Server实践IDEA创建Spring Boot项目,这里重点描述持久层的Mybatis实现。持久层的Mybatis实现需要同时完成接口和xml两个部分。

1.Dao/Mapper包中的接口实现:

package cn.jeremy.xxx.dao;

import cn.jeremy.xxx.entity.Report;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface ReportMapper {
    public List<Report> selectAll();

    public Report selectOne(@Param("uid")String uid);

	public int updateStatus(@Param("uid")String uid, @Param("status") int status);

    public int updateUpdateAt(@Param("uid")String uid, @Param("current") String current);
}
  • 定义和实体层对应的Mapper接口,使用@Mapper注解;
  • 定义select、update和delete成员函数,这些函数均对应一个数据库操作,但并不使用Java来显式实现,而是在xml文件中实现,再由Mybatis代为管理,自动注入到类对象中;
  • @Param用来映射Java函数参数与xml参数之间的对应关系;

2.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="cn.jeremy.xxx.dao.ReportMapper">
    <resultMap id="resultMap" type="cn.jeremy.pdf_extractor.entity.Report">
        <id property="uid" javaType="java.lang.String" column="uuid" jdbcType="CHAR"></id>
        <result property="name" javaType="java.lang.String" column="name" jdbcType="CHAR"></result>
        <result property="status" javaType="java.lang.Integer" column="status" jdbcType="INTEGER"></result>       
        <result property="updateAt" javaType="java.sql.Timestamp" column="lastupdate_at" jdbcType="TIMESTAMP"></result>        
    </resultMap>

    <sql id="Base_Column_List">
        uuid, status, lastupdate_at
    </sql>

    <select id="selectAll" resultMap="resultMap">
        select * from xxx
    </select>

    <select id="selectOne" resultMap="resultMap">
        select <include refid="Base_Column_List"/> from xxx
        where uuid = #{uid, jdbcType=CHAR}
    </select>

    <update id="updateStatus">
        update xxx
        set status = #{status, jdbcType=INTEGER}
        where uuid = #{uid, jdbcType=CHAR}
    </update>

    <update id="updateUpdateAt">
        update xxx
        set update_at = '#{current, jdbcType=TIMESTAMP}'
        where uuid = #{uid, jdbcType=CHAR}
    </update>
</mapper>
  • 文件开头的两行不能省略;
  • namespace表明了该xml文件是和哪个持久层接口对应;
  • resultMap是映射java变量和数据库表属性的关键,它给出了实体层类变量和属性之间的一一对应关系,可以定义了之后在下面的增删查改标签中直接引用;
  • SQL Server数据属性和Java数据类型的对应可以参考博文:sqlserver与java数据类型对应
  • Java类型和jdbcType的对应可以参考博文:JdbcType
  • <select><update>标签映射一种数据库操作,它们的id绑定的是接口的成员函数;
  • resultType也是映射变量类型和数据库属性类型的一种方式,它直接表明对应的实体层类名,resultTyperesultMap只能同时使用一个,可以参考博文:resultMap和resultType区别详解
  • 如果不使用resultMap而用resultType,Mybatis会自动在函数返回值类型和数据库查询结果建立映射,依据是数据库属性名称和实体层类变量名称之间的关系。要使用自动映射,首先要在application.yml中将map-underscore-to-camel-case设置为true,其次是所有的Java变量名称为小驼峰命名,数据库属性名称为下划线命名,且除命名方法不同外它们之间字母需要完全相同;
  • 如果Mybatis找不到映射关系(比如resultMap中没写映射关系或者写错,又或者自动映射命名之间没有完全对应),则Java变量会赋初始默认值;
  • <sql>标签相当于定义了一个变量,可以插入在下方的增删查改SQL语句中,减少重复;

补充:

  • insert的操作参考下面:
<insert id="createMessage" parameterType="cn.jeremy.xxx.entity.Message">
    insert into <include refid="Table"/>(<include refid="Base_Column_List"/>)
    values(#{message.uid, jdbcType=CHAR}, #{message.reportUid, jdbcType=CHAR}, 
    #{message.createAt, jdbcType=TIMESTAMP}, #{message.code, jdbcType=INTEGER}, 
    #{message.jsonData, jdbcType=VARCHAR})
</insert>
  • 用实体类来封装好一行记录,然后将类对象作为参数传入;
  • xxx.xxx的形式来对应调用;
  • 传入参数类型对应关系推荐用parameterType,不推荐用parameterMap(已被官方丢弃,也很难找到可参考的案例了);
  • 返回参数类型对应关系推荐用resultMap,不推荐用resultType(虽然能自动映射,但要求bean的命名和数据表命名完全对应,弹性空间小);
  • 关于xml文件的官方文档在这里:https://mybatis.org/mybatis-3/sqlmap-xml.html

五、使用Mybatis的数据库操作

在封装好业务层之后,便可以在Springboot框架中使用Mybatis操作数据库了。使用的场景可以分成两类:

1.在Controller中使用

  • 这种使用方式是普遍的使用方式;
  • 首先用@RestController表明当前类是Controller;
  • 然后用@Autowired引入业务层接口,Mybatis会在使用时自动注入类对象;
  • 使用的时候和普通的类内对象一样使用即可。
@RestController
public class ReportController {

    @Autowired
    private ReportService reportService;

    @RequestMapping(value = "/getAllReports", method = RequestMethod.GET)
    public List<Report> getAllReports() {
        return reportService.getAllReports();
    }
}

2.不在Controller中使用
除了在controller使用,有时也需要在自己编写的工具类,也就是util包中的类中调用数据库,但由于这些类没有使用Controller注解,所以是没有办法直接让Mybatis注入Service层接口的。可以采用下面的方式:

  • 主要参考博文:spring在非controller里使用service层的注解
  • 首先用@Component让Springboot知道这个是需要自动注入的类;
  • 在类内定义一个静态同类型对象,在init()函数中指向本对象实例;
  • 使用@PostConstruct函数修饰init()函数,让它在对象生成之后被自动调用,完成依赖注入;
  • 然后就可以像Controller一样使用业务层接口了。
package cn.jeremy.xxx.util;

import cn.jeremy.xxx.service.ReportService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class Consumer implements Runnable {

    @Autowired
    private ReportService reportService;  // Service层接口

    public static Consumer staticConsumer;
    @PostConstruct
    public void init() {
        staticConsumer = this;  // 用一个静态对象指向当前实例
    }

    public Consumer() {

    }

    @Override
    public void run(){
        System.out.println("Consumer thread is running.");        
        // 更新数据库:status = 101
        staticConsumer.reportService.updateStatus(uid, 101);  // 用静态对象调用Service层接口
    }
}

六、Springboot的初始化操作

有些功能或者执行需要在Springboot启动之后就执行,或者需要贯穿整个程序的生命周期(例如某些监听的行为),这时候需要实现Springboot的初始化操作。方法这里介绍以下两种:

1.ApplicationRunner接口的用法

  • 在Springboot的程序主入口实现,更像是main函数的形式,适合完成一些模块化的初始化,例如启动某些组件;
  • 需要实现ApplicationRunner接口;
  • 可以使用从命令行传入的参数;
package cn.jeremy.xxx;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootMain implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) {
        // 创建动态配置
        String[] sourceArgs = args.getSourceArgs();
        for(String a: sourceArgs) {
            System.out.println(a);
        }
        // 下方可以编写Springboot的初始化操作
    }

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

2.@PostConstruct注解的用法

七、打包Springboot程序

最终发布的时候是需要把项目打包部署的,主要参考博客:maven–使用Idea打包SpringBoot项目–方法/实例

  • 首先在pom.xml中添加构建项:
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
  • 注意<build>标签项和<dependencies>同级;
  • 然后运行maven菜单->生命周期->package即可;
  • 打包好的jar包在target文件夹中;
  • 运行jar包命令如下:
java -jar xxx.jar 参数1 参数2 ...参数n

补充1:关于Snowflake算法生成唯一id

  • 首先引入依赖:
<dependency>
    <groupId>com.github.bingoohuang</groupId>
    <artifactId>idworker-client</artifactId>
    <version>0.0.8</version>
</dependency>
  • 然后引入包:
import org.n3r.idworker.Sid;
  • 最后直接使用静态函数调用:
this.uid = Sid.next();
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值