写在前面
这里介绍如何使用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
也是映射变量类型和数据库属性类型的一种方式,它直接表明对应的实体层类名,resultType
和resultMap
只能同时使用一个,可以参考博文: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注解的用法
- 这种方法前面已经使用过了,适合零碎化启动功能组件类中的某些功能;
- 某些无法在构造函数中实现的功能可以在
@PostConstruct
修饰的函数中实现; - 执行顺序是:构造函数 > @Autowired > @PostConstruct,参考博客:SpringBoot 学习之 @PostConstruct、 @Autowired与构造函数的执行顺序;
- 和ApplicationRunner相比,执行顺序是: @PostConstruct > ApplicationRunner;
- 其余还有一些可以实现Springboot初始化操作的方式,参考博客:六种方式,教你在SpringBoot初始化时搞点事情!和Springboot启动执行特定代码的几种方式。
七、打包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();