文章目录
- 注解工具使用的优缺点及注意事项
- 优点
- 缺点
- 注意事项
- 实体类配置 & 相关注解的使用
- 一对多(多对一)
- 一对一
- 多对多
- mprelation 关联查询的使用
- pom文件中引入mprelation依赖
- 配置AutoMapper(扫描被注解的实体类)
- 在实体类中配置注解(更多注解参见上面注解部分,此处只列出其中一个)
- Service层、Mapper层的使用
- 测试调用
- 结果输出
- AutoMapper类中的常用方法
对于一对一,一对多,多对一,多对多的关联查询,MyBatis-Plus 在处理时,需要编写关联查询方法及配置resultMap,并且书写SQL。对于这种操作,我们可以使用注解来进行简化。
MyBatis-Plus-Relation ( mprelation ) : 实现 mybatis-plus 一对一、一对多、多对一、多对多的自动关联查询,注解方式。
mprelation
源码地址:
- GitHub:https://github.com/dreamyoung/mprelation.git
- Gitee:https://gitee.com/dreamyoung/mprelation.git
mprelation_demo
地址:
- GitHub:https://github.com/dreamyoung/mprelation_demo.git
- Gitee:https://gitee.com/dreamyoung/mprelation_demo.git
POM依赖:
<dependency>
<groupId>com.github.dreamyoung</groupId>
<artifactId>mprelation</artifactId>
<version>0.0.3.2-RELEASE</version>
</dependency>
注解工具使用的优缺点及注意事项
优点
- 使用简单,通过在实体类上添加
@OneToOne
/@OneToMany
/@ManyToOne
/@ManyToMany
等注解即可使用。 - 注解命名参考了Hibernate命名,如果使用过Hibernate可轻松上手。
- 1对1、1对多、多对1、多对多映射时,可以不再编写SQL及XML配置文件,免去了配置冗长的
<resultMap>
的麻烦。 - Service层及Mapper层不需要再添加
getLinkById
、selectLinkById
之类的方法来关联映射。 - 重写过的
ServiceImpl
各种内置的查询方法都自动关联查询,非内置方法可以调用autoMapper
相关方法进行自动或手动关联。 - 所有执行采用非
join
方式查询,同时解决关联处理的1+n
问题。
缺点
- 目前只针对
SqlSession / Mapper
形式有效(ActiveRecord
形式暂未涉及修改,也没有测试)。 - 非事务下, 1个连接(1个
SqlSession
)只执行一条SQL,而自动获取每个关联属性的sql都会创建1-2个SqlSession
(并执行1-2条SQL)。如果需要使用非内置方法(即除ServiceImpl
外的方法),必须配置只读事务来减少SqlSession
创建。 - 如果有多个延迟加载的关联属性,类上可注解
@AutoLazy(false)
或不标注该注解(默认),之后通过initialize()
方法在事务范围内的一个SqlSession
中同时加载需要的多个延迟加载的属性。
注意事项
- 非
ServiceImpl
内置的业务查询,配置事务管理,减少SqlSession
的创建。 - 实体上可用注解
@AutoLazy(true/false)
来标注是否自动触发延迟加载,该注解只针对需要延迟的属性。
-
true
或无值的话,则获取延迟的关联属性时自动关联。但每一个延迟属性的获取都消耗一个SqlSession
。适合于只有一个延迟属性的情况。 -
false
或者不标注该注解的话,需要手动通过initialize()
方法对延迟的关联属性进行获取,否则不会自动关联获取,此时关联为空。适合于有多个延迟属性的情况。
- 如果可以,不使用延迟加载(延迟加载的使用是在
SqlSession
关闭后执行的,需要重新创建SqlSession
)。 - 如果确实需要延迟加载,可使用
ServiceImpl
或AutoMapper
相关的initialize()
方法一次性加载所有需要的被延迟的属性(只需要创建额外的一个SqlSession
,毕竟SqlSession
之前已经关闭)。
实体类配置 & 相关注解的使用
一对多(多对一)
- 一对多(多对一)表结构:
company:(compnay_id, name)
man:(man_id, name, company_id)
Company实体类
@Data
public class Company {
@TableId(value = "company_id")
private Long id;
private String name;
// 一对多
@TableField(exist = false)
@OneToMany // 一对多默认为延迟加载,即@Lazy/@Lazy(true)/或此时不标注
// name:本类中的关联字段名,referencedColumnName:Man中的关联字段名
@JoinColumn(name = "company_id", referencedColumnName = "company_id")
private Set<Man> employees;
}
Man实体类
@Data
public class Man {
@TableId(value = "man_id")
private Long id;
private String name;
// 多对一
@TableField("company_id")
private Long companyId;
@TableField(exist = false)
@ManyToOne // 多对一默认为立即加载,即@Lazy(false)或此时不标注
// Man中相关的@TableField与Company中的@TableId名称一致时@JoinColumn可省略
@JoinColumn(name = "company_id", referencedColumnName = "company_id")
private Company company;
}
一对一
- 一对一表结构:(实际可以减少一方)
woman:(woman_id, name, lao_gong_id)
man:(man_id, name, lao_po_id)
Woman实体类
@Data
public class Woman {
@TableId(value = "woman_id")
private Long id;
private String name;
// 一对一
@TableField("lao_gong_id")
private Long laoGongId;
@TableField(exist = false)
@OneToOne // 一对一默认为立即加载,即@Lazy(false)或此时不标注
@JoinColumn(name = "lao_gong_id", referencedColumnName = "man_id")
private Man laoGong;
}
Man实体类
@Data
public class Man {
@TableId(value = "man_id")
private Long id;
private String name;
// 一对一
@TableField("lao_po_id")
private Long laoPoId;
@TableField(exist = false)
@OneToOne
@JoinColumn(name = "lao_po_id", referencedColumnName = "woman_id")
private Woman laoPo;
}
多对多
- 多对多表结构:
course:(course_id, name)
child:(child_id, name)
student_course:(id, student_id, course_id)
Course实体类
@Data
public class Course {
@TableId(value = "course_id")
private Long id;
private String name;
// 多对多
@TableField(exist = false)
@ManyToMany // 多对多默认为延迟加载,即@Lazy(true)或此时不标注
// 第三方命名为StudentCourseMapper或CourseStudentMapper时@JoinTable注解一般可省略
@JoinTable(targetMapper = StudentCourseMapper.class)
@JoinColumn(name = "course_id", referencedColumnName = "course_id")
@InverseJoinColumn(name = "child_id", referencedColumnName = "student_id")
private List<Child> students;
}
Child实体类
@Data
public class Child {
@TableId("child_id")
private Long id;
private String name;
// 多对多
@TableField(exist = false)
@ManyToMany
@JoinTable(targetMapper = StudentCourseMapper.class)
@JoinColumn(name = "child_id", referencedColumnName = "student_id")
@InverseJoinColumn(name = "course_id", referencedColumnName = "course_id")
private List<Course> courses;
}
StudentCourse中间类(多对多必须要有,如果命名为StudentCourse或CourseStudent,则上边的@JoinTable可省略)
@Data
public class StudentCourse {
// 可以有也可以无此ID
private Long id;
@TableField("student_id")
private Long studentId;
@TableField("course_id")
private Long courseId;
}
mprelation 关联查询的使用
pom文件中引入mprelation依赖
<dependency>
<groupId>com.github.dreamyoung</groupId>
<artifactId>mprelation</artifactId>
<version>0.0.3.2-RELEASE</version>
</dependency>
配置AutoMapper(扫描被注解的实体类)
@Configuration
public class AutoMapperConfig {
@Bean
public AutoMapper autoMapper() {
// 配置实体类所在目录(可多个,暂不支持通过符号*进行配置)
return new AutoMapper(new String[]{"demo.entity", "demo.bean"});
}
}
在实体类中配置注解(更多注解参见上面注解部分,此处只列出其中一个)
@Data
// 不需要手动触发加载延迟属性,当延迟属性被调用时自动创建Session进行获取。可见如果有多个延迟属性需要被使用时,会造成多次创建Session,此时可以标注为AutoLazy(false)或不标注,然后采用initialze方法手动一次性加载需要的属性
@AutoLazy
public class Man {
@TableId(value = "man_id")
private Long id;
private String name;
private Long laoPoId;
@TableField(exist = false)
@OneToOne
@JoinColumn(name = "lao_po_id", referencedColumnName = "woman_id")
private Woman laoPo;
@TableField("company_id")
private Long companyId;
@TableField(exist = false)
@ManyToOne
@JoinColumn(name = "company_id", referencedColumnName = "company_id")
private Company company;
@TableField(exist = false)
@OneToMany
@JoinColumn(name = "man_id", referencedColumnName = "lao_han_id")
@Lazy(false)
private List<Child> waWa;
@TableField(exist = false)
@OneToMany
@JoinColumn(name = "man_id", referencedColumnName = "man_id")
@Lazy(false)
private Set<Tel> tels;
}
Service层、Mapper层的使用
以下是基于MyBatis-Plus官方示例修改而来的测试程序
Mapper
接口:
public interface ManMapper extends BaseMapper<Man> {}
Service
接口:
public interface IManService extends IService<Man> {} // IService为重写过的同名接口
Service
实现:
@Service
public class ManServiceImpl extends ServiceImpl<ManMapper, Man> implements IManService {} // ServiceImpl为重写过的同名接口
测试调用
public class ServiceTest {
@Autowired
ManService manService;
@Test
public void t_man_serviceImpl() {
Man man = manService.getById(1); // 原MP的ServiceImpl的各种查询,被重写过后,都可以自动关联
System.out.println(man);
}
}
结果输出
Man(
id=1,
name=程序猿小明,
laoPoId=1,
laoPo=Woman(id=1, name=程序猿小明老婆, laoGongId=1, laoGong=null, waWa=null),
companyId=1,
company=Company(id=1, name=百度, employees=null),
waWa=[
Child(id=1, name=xxx1, lao_han_id=null, laoHan=null, lao_ma_id=null, laoMa=null, courses=null),
Child(id=2, name=xxx2, lao_han_id=null, laoHan=null, lao_ma_id=null, laoMa=null, courses=null)
],
tels=[
Tel(id=1, tel=139xxxxxx, manId=1, laoHan=null),
Tel(id=4, tel=159xxxxxx, manId=1, laoHan=null),
Tel(id=2, tel=137xxxxxx, manId=1, laoHan=null)
]
)
如果需要对其关联属性对象的关联属性进行自动加载,可以继续使用AutoMapper
对象的mapperEntity
、mapperEntityList
、mapperEntitySet
、mapperEntityPage
等方法来操作:
// 获取(填充)waWas 的关联
List waWas = man.getWaWas();
autoMapper.mapperEntityList(waWas);
AutoMapper类中的常用方法
方法名称 | 方法说明 |
| 对一个实体类,实现自动关联 |
| 对一个实体类 |
| 对一个实体类 |
| 对一个实体类 |
| 对一个实体类 |
★ | 统一以上各种方法,直接对实体类,列表,分页都可以实现自动关联 |
★ | 对一个 该方法在重写过的 |
AutoMapper在重写过的ServiceImpl类中已经自动注入可用(名为autoMapper),其它情况也可以手动注入:
public class MPRTest2 {
@Autowired
AutoMapper autoMapper;
@Resource
private ManMapper manMapper;
@Test
public void t_man() {
Man man = manMapper.selectById(1L);
autoMapper.mapperEntity(man);
System.out.println(man);
}
}