Mybatis注解开发指北
目录
0. Mybatis注解开发步骤
- 导入Mybatis、Mysql及相关辅助包
- 配置数据库连接
- 创建数据库对应的实体类(entity)
- 创建实体类对应的Dao/Mapper实现增删改查
- 创建相应的测试类
1. 导入相关配置文件
- 用maven创建工程,在
pom.xml中的dependencies
中导入如下代码:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
- 同时可以导入mysql、log4j和junit用来测试和输出文档测试
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
- 在resources下新建
log4j.properties
文件将以下内容拷贝进去,用于输出日志等相关配置。
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=/axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
2. 配置数据库连接
-
配置数据库连接是为了连接数据库以便进行CRUD操作
-
配置数据库连接,在resources文件夹中新建
SqlMapConfig.xml
储存数据库相关配置,配置如下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--mybatis主配置文件-->
<configuration>
<!-- 配置类型别名-->
<typeAliases>
<package name="com.database.mybatis.entity"/>
</typeAliases>
<!-- 配置环境-->
<environments default="mysql">
<environment id="mysql">
<!-- 事务类型-->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源-->
<dataSource type="POOLED">
<!-- 配置连接数据库的基本信息-->
<!--Mysql 6.0之前driver 驱动写com.mysql.jdbc.Driver-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="qwer123"/>
</dataSource>
</environment>
</environments>
<!-- 指定dao接口所在位置-->
<mappers>
<package name="com.database.mybatis.dao"></package>
</mappers>
</configuration>
-
类型别名的作用:
- 类型别名的作用是为Java类型折翼短的名字,至于xml配置有关,可以减少类完全限定名的冗余。单个配置的时候可以是:
<typeAliases> <typeAlias alias="Student" type="com.database.mybatis.entity.Student"/> </typeAliases>
- 也可以进行批量配置,在如下方法中配置别名,Mybatis可以在entity下搜索需要的Java Bean的瘦子米小写的非限定类名作为他的别名,比如
com.database.mybatis.entity.Student
的别名是student
。
<typeAliases> <package name="com.database.mybatis.entity"/> </typeAliases>
3. 创建数据库对应的实体类(entity)
-
创建实体类的作用是为了将存放数据库中返回的数据,以便于进行相应的操作。
-
创建实体类之前应先建立数据库相应的表,提供测试的数据库表有3个:Teacher、Class、Student。三个表是多对多的关系,EER图如下:
- sql语句如下:
CREATE DATABASE test;
use test;
CREATE TABLE student(
id INT PRIMARY KEY,
teacher_id INT,
class_id INT,
student_nub BIGINT NOT NULL,
student_name VARCHAR(20) NOT NULL,
student_sex VARCHAR(20) DEFAULT '男',
student_phone VARCHAR(20)
);
SELECT * FROM student;
INSERT INTO student VALUES(1,1,1,1800300101,'一班一号','男',NULL);
CREATE TABLE class(
id INT PRIMARY KEY,
teacher_id INT,
student_id INT,
class_nub BIGINT NOT NULL,
class_name VARCHAR(20) NOT NULL,
class_maxperson INT NOT NULL,
class_haveperson INT NOT NULL,
class_place INT NOT NULL
);
INSERT INTO class VALUES(1,1,1,300101,'三院一年级一号课',80,0,17101);
SELECT * FROM class;
CREATE TABLE teacher(
id INT PRIMARY KEY,
student_id INT,
class_id INT,
teacher_nub BIGINT NOT NULL,
teacher_name VARCHAR(20) NOT NULL,
teacher_sex VARCHAR(20) DEFAULT '男',
teacher_phone VARCHAR(20)
);
INSERT INTO teacher VALUES(1,1,1,003001,'三院一号教师','男','13113311331');
SELECT * FROM teacher;
alter table student add foreign key(class_id) references class(id);
alter table student add foreign key(teacher_id) references teacher(id);
alter table teacher add foreign key(class_id) references class(id);
alter table teacher add foreign key(student_id) references student(id);
alter table class add foreign key(teacher_id) references teacher(id);
alter table class add foreign key(student_id) references student(id);
-
创建实体类一般放入entiy包下,目录结构如下:
-
实体类创建时尽量遵循阿里巴巴java开发手册命名规则,养成良好习惯。数据库中表的字段类型需要与Java类型相对应,Java变量名以表字段名全称命名,例如表字段为student_id,则Java变量名应为StudentId。
-
sql变量类型与Java变量对照表如下:
Java类型 | SQL类型 |
---|---|
boolean | BIT |
byte | TINYINT |
short | SMALLINT |
int | INTEGER |
long | BIGINT |
String | CHAR,VARCHAR,LONGVARCHAR |
java.sql.Date | DATE |
java.sql.Time | TIME |
java.sql.Timestamp | TIMESTAMP |
- Class实体类如下:
public class Class implements Serializable {
private Integer id;
private Integer teacherId;
private Integer studentId;
private BigInteger classNub;
private String className;
private Integer classMaxPerson;
private Integer classHavePerson;
private Integer classPlace;
}
- 然后创建实体类对应的getter、setter和toString。完整的Class实体类如下:
package com.database.mybatis.entity;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.List;
/**
* @author linxi
* @function
* @project database
* @package com.database.mybatis.model
* @date 2020/5/8-1:37 下午
*/
public class Class implements Serializable {
private Integer id;
private Integer teacherId;
private Integer studentId;
private BigInteger classNub;
private String className;
private Integer classMaxPerson;
private Integer classHavePerson;
private Integer classPlace;
@Override
public String toString() {
return "Class{" +
"id=" + id +
", teacher_id=" + teacherId +
", student_id=" + studentId +
", class_nub=" + classNub +
", class_name='" + className + '\'' +
", class_maxperson=" + classMaxPerson +
", class_haveperson=" + classHavePerson +
", class_place=" + classPlace +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getTeacherId() {
return teacherId;
}
public void setTeacherId(Integer teacherId) {
this.teacherId = teacherId;
}
public Integer getStudentId() {
return studentId;
}
public void setStudentId(Integer studentId) {
this.studentId = studentId;
}
public BigInteger getClassNub() {
return classNub;
}
public void setClassNub(BigInteger classNub) {
this.classNub = classNub;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public Integer getClassMaxPerson() {
return classMaxPerson;
}
public void setClassMaxPerson(Integer classMaxPerson) {
this.classMaxPerson = classMaxPerson;
}
public Integer getClassHavePerson() {
return classHavePerson;
}
public void setClassHavePerson(Integer classHavePerson) {
this.classHavePerson = classHavePerson;
}
public Integer getClassPlace() {
return classPlace;
}
public void setClassPlace(Integer classPlace) {
this.classPlace = classPlace;
}
}
4. 创建实体类对应的Dao/Mapper实现增删改查
- Dao和Mapper在注解开发中已经没有明显区别,所以不作区分。在Mybatis注解开发中,只要定义好对应的接口并加上合适的注解即可直接使用,不需要再编写Dao的具体实现类。
- 因为我们实体类中的变量名与数据库表中的字段名不一致,所以我们需要任意一个用@Results注解使变量名与字段名进行统一,在再其他借口上使用@Resultmap()调用统一关系,代码如下。
/**
* results的ID用于定义此对应的名称,在其他接口中直接使用@Resultmap("ID")来使用此对应关系,无需重写。
* 表中主键需要在@Result()中的id属性设为true(默认为false)
* @resylt() 的column属性为表中字段名,property为实体类变量名
*/
@Results(id = "classDao", value = {
@Result(id = true,column = "id",property = "id"),
@Result(column = "teacher_id",property = "teacherId"),
@Result(column = "student_id",property = "studentId"),
@Result(column = "class_nub",property = "classNub"),
@Result(column = "class_name",property = "className"),
@Result(column = "class_maxperson",property = "classMaxPerson"),
@Result(column = "class_haveperson",property = "classHavePerson"),
@Result(column = "class_place",property = "classPlace")
})
4.1 Select查询
-
接口例子:
- 查询Class所有内容,返回值为Class类型的List
/** * results的ID用于定义此对应的名称,在其他接口中直接使用@Resultmap("ID")来使用此对应关系,无需重写。 * 表中主键需要在@Result()中的id属性设为true(默认为false) * @resylt() 的column属性为表中字段名,property为实体类变量名 */ @Results(id = "classDao", value = { @Result(id = true,column = "id",property = "id"), @Result(column = "teacher_id",property = "teacherId"), @Result(column = "student_id",property = "studentId"), @Result(column = "class_nub",property = "classNub"), @Result(column = "class_name",property = "className"), @Result(column = "class_maxperson",property = "classMaxPerson"), @Result(column = "class_haveperson",property = "classHavePerson"), @Result(column = "class_place",property = "classPlace") }) @Select("select * from class") List<Class> findAllClass();
- 通过id查询
@ResultMap("classDao") @Select("select * from class where id=#{id}") List<Class> findClassById(Integer classId);
-
模糊查询
- 方式一:
@Select("select * from class where class_name like '%${value}%'")
这种方式直接传送参数进来就可,不需要加
%
号。例如传送张三作为value即相当于:select * from class where class_name like %张三%。\例子:
@ResultMap("classDao") @Select("select * from class where class_name like '%${value}%'") List<Class> findClassByClassName(String className);
- 方式二:
@Select("select * from class where class_name like #{username}")
这种方式传送参数时需要带
%
,例如传送%张三%
作为value即相当于:select * from class where class_name like %张三%。例子:
@ResultMap("classDao") @Select("select * from class where class_name like #{username}") List<Class> findClassByClassName(String className);
-
多表查询:多表对应关系有两种:一对一和一对多。
-
一对一:假设有表A,B;A表中有字段id和b_id,b_id对应B表的id(id为唯一值),所以A表的每一列都有唯一一个B与其对应,则此关系为一对一。一对一关系需要用@one注解映射。
- 假设查询某节课及其对应的老师,因为每个课程只有一个老师,所以关系为一对一。
- 首先需要在Class实体类中建立Teacher类型变量用于存储老师信息,并建立相应的gettter和setter。
private Teacher teacher; public Teacher getTeacher() { return teacher; } public void setTeacher(Teacher teacher) { this.teacher = teacher; }
- 第二步,需要在TeacherDao中建立通过classID查询teacher的接口。
@Select("select * from teacher where class_id=#{class_id}") Teacher findTeacherByClassId(Integer classId);
- 第三步,在ClassDao中建立通过classId查询课程及其对应老师的接口。实质上是查询class但是class实体类中的Teacher中要包含课程对应的老师。多表查询的@one注解是属于@result的参数。
@Results(value = { @Result(id = true, column = "id", property = "id"), @Result(column = "teacher_id", property = "teacherId"), @Result(column = "student_id", property = "studentId"), @Result(column = "class_nub", property = "classNub"), @Result(column = "class_name", property = "className"), @Result(column = "class_maxperson", property = "classMaxPerson"), @Result(column = "class_haveperson", property = "classHavePerson"), @Result(column = "class_place", property = "classPlace"), @Result(property = "teacher", column = "teacherId", one = @One(select = "com.database.mybatis.dao.TeacherDao.findTeacherByClassId", fetchType = FetchType.EAGER)) }) @Select("select * from class where id = #{id}") Class findClassAndTeacherByClassId(Integer classId);
-
一对多:假设有表A,B;A表中有字段id和b_room,b_room对应B表的room(room不唯一),所以A表的每一列都有多个B与其对应,则此关系为一对多。一对多关系需要用@many注解映射
- 假设查询上某节课的所有学生,因为一个课程有多名学生,所以关系为一对多。
- 首先需要在Class实体类中建立List类型变量用于存储老师信息,并建立相应的gettter和setter。
private List<Student> students; public List<Student> getStudents() { return students; } public void setStudents(List<Student> students) { this.students = students; }
- 第二步,需要在StudentDao中建立通过classID查询student的接口。
@Select("select * from student where class_id=#{class_id}") List<Student> findStudentByClassId(Integer classId);
- 第三步,在ClassDao中建立通过classId查询课程及其对应学生的接口。实质上是查询class但是class实体类中List中要包含课程对应的学生。多表查询的@many注解是属于@result的参数。
@Select("select * from class where id=#{id}") @Results(value = { @Result(id = true,column = "id",property = "id"), @Result(column = "teacher_id",property = "teacherId"), @Result(column = "student_id",property = "studentId"), @Result(column = "class_nub",property = "classNub"), @Result(column = "class_name",property = "className"), @Result(column = "class_maxperson",property = "classMaxPerson"), @Result(column = "class_haveperson",property = "classHavePerson"), @Result(column = "class_place",property = "classPlace"), @Result(property = "students", column = "id", many = @Many(select = "com.database.mybatis.dao.StudentDao.findStudentByClassId", fetchType = FetchType.LAZY)) }) List<Class> findClassAndStudentsByClassId(Integer classId);
-
4.1.1 立即加载EAGER和延迟加载LAZY
- 延迟加载和立即加载的区别:
- 顾名思义,立即加载就是在sql语句执行的时候直接从数据库取出数据,而延迟加载就是虽然sql语句执行,但是只有在用到相应的数据的时候才从数据库加载出来。
- 什么时候用延迟加载和立即加载:
- 延迟加载多用于查询一对多的时候,立即加载多用于查询一对一的时候,但并不绝对。
- 为什么要用延迟加载:
- 使用延迟加载的目的是为了减少系统资源的消耗,例如查询课程和选择此课程的学生的时候,当一个课程中只有几十个学生的时候,我们使用延迟加载和立即加载对系统资源的消耗并不明显,但是当一个课程有几千万个学生的时候,同时我们并不是立刻使用所有学生的信息。我们如果使用立即加载就会造成大量浪费系统资源,此时使用延迟加载的随用随取模式就会有很大的优势。
4.2 Insert插入
- 插入数据到表中要使用@Insert注解
例子:
@ResultMap("classDao")
@Insert("insert into class(id,teacher_id,student_id,class_nub,class_name,class_maxperson,class_haveperson,class_place) values(#{id},#{teacher_id},#{student_id},#{class_nub},#{class_name},#{class_maxperson},#{class_haveperson},#{class_place})")
void insertClass(Class aClass);
此中的所有字段都在Class实体类中,只要在#{}
中填入对应的字段名或者别名并将Class作为参数传入,Mybatis就会在Class中自动查找对应的关系。
4.3Update更新
- 更新数据到表中要使用@Update注解
例子:
@ResultMap("classDao")
@Update("update class set teacher_id=#{teacher_id},student_id=#{student_id},class_nub=#{class_nub},class_name=#{class_name},class_maxperson=#{class_maxperson},class_haveperson=#{class_haveperson},class_place=#{class_place} where id=#{id}")
void updateClassById(Class aClass);
4.4Delete删除
- 从表中删除数据使用@Delete注解
例子:
@ResultMap("classDao")
@Delete("delete from class where id=#{id}")
void deleteClassById(Integer classId);
5 创建相应的测试类
- text.java下创建测试类,测试类的目录结构最好与项目结构相同。所以我们在text.java.com.database.mybatis下创建测试类
testClass.java
。
- 想要能够使用我们创建好的接口操作数据库需要如下几个步骤:
- 读取数据库配置文件:用于连接数据库及其相关配置
- 使用建造者模式创建SqlSessionFactory工厂:使用建造者模式可以隐藏创建工厂的细节,直接将我们需要的工厂的“图纸”传入即可建造出我们想要的工厂。
- 使用工厂模式创建SqlSession对象:使用工厂模式创建SqlSession对象可以解耦,可以在不改变源码的方式进行操作
- 使用SqlSession创建Dao接口的代理对象执行方法:在不修改源码的基础上对已有方法增强,可是已实现不写Dao的实现类,就可以实现功能。
- 释放资源
例子:
//读取数据库配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//建造者模式创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//使用工厂模式生产SqlSession对象
SqlSession session = factory.openSession();
//使用SqlSession创建Dao接口的代理对象执行方法
ClassDao classDao = session.getMapper(ClassDao.class);
//使用代理对象执行方法
List<Class> aClass = classDao.findAll();
for (Class aClass1 : aClass) {
System.out.println(aClass1);
System.out.println(aClass1.getStudents());
}
- 但是在实际生产环境中我们可以更简单的进行操作:
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml").getMapper(ClassDao.class);
List<Class> aClass = classDao.findAll();
for (Class aClass1 : aClass) {
System.out.println(aClass1);
System.out.println(aClass1.getStudents());
}
5.1 使用junit创建测试类
- 创建TestClass.java
- 在进行测试前我们需要进行上述配置拿到Dao的代理对象
public class TestClass {
private InputStream in;
private SqlSessionFactory factory;
private SqlSession session;
private ClassDao classDao;
@Before
public void init() throws IOException {
in = Resources.getResourceAsStream("SqlMapConfig.xml");
factory = new SqlSessionFactoryBuilder().build(in);
session = factory.openSession();
classDao = session.getMapper(ClassDao.class);
}
- 在测试结束后我们需要释放资源,所以增加如下方法:
@After
public void destroy() throws IOException {
session.commit();
session.close();
in.close();
}
- 以通过id查询课程为例创建测试类。
- 创建testFindClassById方法。
- 通过代理对象调用findClassById方法并传入参数。
- 打印查询结果。
@Test
public void testFindClassById(){
List<Class> classes = classDao.findClassById(1);
for (Class aClass : classes) {
System.out.println(aClass);
}
}