使用AOP+反射实现自定义Mybatis多表关联查询
一、需求
目前使用的ORM框架是Mybatis Plus,是Mybatis的增强框架,基础的CRUD的方法都集成了,开发起来很是方便。但是项目中总是需要多表关联查询。
Mybatis的多表关联有两种
一、在Mapper中使用@Result @One @Many注解
二、在xml文件中配置对应的resultMap和关联标签
使用起来很不方便。JPA倒是有多表关联的注解实现,但是不想再引入另一个ORM框架。
目前的需求是增强现有的查询,使用简单的注解即可实现多表关联。
二、核心代码
GitHub:https://github.com/sushengbuyu/mybatis-mapping-demo
实现该功能总共需要四个文件
两个自定义注解,一个虚拟Mapper,一个切面处理类
源码
MapTo
自定义映射注解,标注需要映射处理的字段
import java.lang.annotation.*;
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MapTo {
Class<?> targetClass();
String sql();
boolean doDeep() default false;
}
DoMap
自定义映射处理注解,标注需要执行映射的方法
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoMap {
Class<?> targetClass();
String spel() default "";
}
IDualMapper
虚拟Mapper,用来执行自定义SQL
import java.util.List;
import java.util.Map;
@Mapper
public interface IDualMapper {
List<Map<String, Object>> executeSql(String sql);
}
DualMapper
使用者自行实现DualMapper,解除mybatis强依赖
package sushengbuyu.maptodemo.sys.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import sushengbuyu.maptodemo.aop.IDualMapper;
import java.util.List;
import java.util.Map;
@Mapper
public interface DualMapper extends IDualMapper {
@Select("${sql}")
List<Map<String, Object>> executeSql(@Param("sql") String sql);
}
DoMapAspect
切面处理类,核心代码,执行映射操作
package sushengbuyu.maptodemo.aop;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
@Component
@Aspect
public class DoMapAspect {
private final static Logger log = LoggerFactory.getLogger(DoMapAspect.class);
private static final Map<Class<?>, Set<String>> MAPPING = new HashMap<>(8);
private final IDualMapper dualMapper;
public DoMapAspect(DualMapper dualMapper) {
this.dualMapper = dualMapper;
}
@PostConstruct
public void initMap() {
// 初始化所有MapTo对象
// 扫描所有类
Set<Class<?>> classes = ClassUtil.scanPackage("sushengbuyu.maptodemo");
int totalField = 0;
// 找出使用MapTo注解的对象
for (Class<?> c : classes) {
Field[] fields = c.getDeclaredFields();
for (Field f : fields) {
if (null != f.getAnnotation(MapTo.class)){
log.info("找到需要映射的字段: 类名:{} - 字段名:{}", c.getName(), f.getName());
// 保存映射关系
Set<String> set;
if (MAPPING.containsKey(c)) {
set = MAPPING.get(c);
} else {
set = new HashSet<>();
}
set.add(f.getName());
MAPPING.put(c, set);
totalField++;
}
}
}
log.info("总计{}个映射类,{}个映射字段", MAPPING.size(), totalField);
}
@Pointcut("@annotation(doMap)")
public void point(DoMap doMap){}
@Around(value = "@annotation(doMap)")
public Object doMap(ProceedingJoinPoint point, DoMap doMap) throws Throwable {
// 执行切面方法
Object obj = point.proceed();
try {
Object relObj = obj;
if (StringUtils.hasLength(doMap.spel())) {
// 如果使用了SPEL表达式,则从返回值中获取处理对象
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(doMap.spel());
relObj = expression.getValue(obj);
}
// 获取映射类
Class<?> c = doMap.targetClass();
// 映射处理
doMapping(c, relObj);
} catch (Exception e) {
log.error("映射异常", e);
}
log.info("返回对象:{}", obj);
return obj;
}
private void doMapping(Class<?> c, Object obj) throws Exception {
if (obj instanceof Collection) {
// 集合
Collection<?> co = (Collection<?>) obj;
for (Object o : co) {
mapping(c, o);
}
} else {
// 单个对象
mapping(c, obj);
}
}
private void mapping(Class<?> c, Object obj) throws Exception {
// 判断是否有映射关系
if (MAPPING.containsKey(c)) {
log.info("处理映射类:{}", c.getName());
// 从缓存中获取映射字段名称
Set<String> filedNames = MAPPING.get(c);
for (String fieldName : filedNames) {
Field f = c.getDeclaredField(fieldName);
log.info("处理映射字段:{}", f.getName());
// 获取映射注解
MapTo mapTo = f.getAnnotation(MapTo.class);
log.info("映射配置:{}", mapTo);
// 设置私有字段访问权限
f.setAccessible(true);
// 执行SQL
String sql = mapTo.sql();
// 处理SQL变量
List<String> res = ReUtil.findAll("\$\{(.*?)}", sql, 0);
log.info("SQL变量:{}", res);
for (String re : res) {
Field ff = obj.getClass().getDeclaredField(re.substring(2, re.length()-1));
ff.setAccessible(true);
Object o = ff.get(obj);
sql = sql.replace(re, o.toString());
}
log.info("最终SQL:{}", sql);
List<Map<String, Object>> results = dualMapper.executeSql(sql);
Object v = null;
if (Collection.class.isAssignableFrom(f.getType())) {
// 集合对象
if (results.size() > 0) {
v = results.stream()
.map(r -> mapToBean(r, mapTo.targetClass()))
.collect(Collectors.toList());
}
} else {
// 单个对象
if (results.size() > 1) {
log.error("预计返回一条数据,实际返回多条数据。执行SQL: {}", sql);
} else if (results.size() == 1) {
// 转换结果,赋值
v = mapToBean(results.get(0), mapTo.targetClass());
}
}
if (v != null && mapTo.doDeep()) {
doMapping(mapTo.targetClass(), v);
}
f.set(obj, v);
}
}
}
private Object mapToBean(Map<?, ?> map, Class<?> clazz) {
try {
return BeanUtil.fillBeanWithMap(map, clazz.newInstance(), true);
} catch (InstantiationException | IllegalAccessException e) {
log.error("实例化异常", e);
return null;
}
}
}
三、使用方法
测试类
SysUser
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import java.util.List;
import java.util.StringJoiner;
public class SysUser implements Serializable {
private static final long serialVersionUID = 4855472141572371097L;
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String username;
private String password;
private String nickName;
@MapTo(targetClass = SysRole.class
, doDeep = true
, sql = "SELECT * FROM sys_role WHERE user_id=${id}")
@TableField(exist = false)
private SysRole sysRole;
@MapTo(targetClass = SysRole.class
, doDeep = true
, sql = "SELECT * FROM sys_role WHERE user_id=${id}")
@TableField(exist = false)
private List<SysRole> roleList;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public SysRole getSysRole() {
return sysRole;
}
public void setSysRole(SysRole sysRole) {
this.sysRole = sysRole;
}
public List<SysRole> getRoleList() {
return roleList;
}
public void setRoleList(List<SysRole> roleList) {
this.roleList = roleList;
}
@Override
public String toString() {
return new StringJoiner(", ", SysUser.class.getSimpleName() + "[", "]")
.add("id=" + id)
.add("username='" + username + "'")
.add("password='" + password + "'")
.add("nickName='" + nickName + "'")
.add("sysRole=" + sysRole)
.add("roleList=" + roleList)
.toString();
}
}
SysRole
package sushengbuyu.maptodemo.sys.po;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import sushengbuyu.maptodemo.aop.MapTo;
import java.util.List;
import java.util.StringJoiner;
public class SysRole {
@TableId
private Long id;
private Long userId;
private String name;
@MapTo(targetClass = SysPermission.class
, sql = "SELECT p.* FROM sys_permission p " +
"LEFT JOIN sys_role_permission rp ON p.id = rp.perm_id " +
"WHERE rp.role_id = ${id}")
@TableField(exist = false)
private List<SysPermission> permissionList;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public List<SysPermission> getPermissionList() {
return permissionList;
}
public void setPermissionList(List<SysPermission> permissionList) {
this.permissionList = permissionList;
}
@Override
public String toString() {
return new StringJoiner(", ", SysRole.class.getSimpleName() + "[", "]")
.add("id=" + id)
.add("userId=" + userId)
.add("name='" + name + "'")
.toString();
}
}
SysPermission
package sushengbuyu.maptodemo.sys.po;
import java.util.StringJoiner;
public class SysPermission {
private Long id;
private String name;
private Integer type;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
@Override
public String toString() {
return new StringJoiner(", ", SysPermission.class.getSimpleName() + "[", "]")
.add("id=" + id)
.add("name='" + name + "'")
.add("type=" + type)
.toString();
}
}
SysUserService
测试用例就常见的三种, 查单个,查列表,查分页
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.io.Serializable;
import java.util.List;
@Service
public class SysUserService extends ServiceImpl<SysUserMapper, SysUser> {
@DoMap(targetClass = SysUser.class)
@Override
public SysUser getById(Serializable id) {
return super.getById(id);
}
@DoMap(targetClass = SysUser.class)
public List<SysUser> listAll() {
QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
return baseMapper.selectList(wrapper);
}
@DoMap(targetClass = SysUser.class, spel = "records")
public Page<SysUser> page() {
QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
Page<SysUser> p = new Page<>(1, 10);
return baseMapper.selectPage(p, wrapper);
}
}
DoMapTests
import cn.hutool.json.JSONUtil;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
//@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class DoMapTests {
@Autowired
private SysUserService service;
@Test
void single() {
System.out.println(JSONUtil.toJsonPrettyStr(service.getById(1)));
}
@Test
void list() {
System.out.println(JSONUtil.toJsonPrettyStr(service.listAll()));
}
@Test
void page() {
System.out.println(JSONUtil.toJsonPrettyStr(service.page()));
}
}
测试数据
测试结果
single
{
"nickName": "aa11",
"roleList": [
{
"permissionList": [
{
"type": 0,
"name": "add",
"id": 1
},
{
"type": 0,
"name": "query",
"id": 2
}
],
"userId": 1,
"name": "r1",
"id": 11
},
{
"permissionList": [
{
"type": 0,
"name": "del",
"id": 3
}
],
"userId": 1,
"name": "r2",
"id": 12
}
],
"password": "123456",
"id": 1,
"username": "a1"
}
list
[
{
"nickName": "aa11",
"roleList": [
{
"permissionList": [
{
"type": 0,
"name": "add",
"id": 1
},
{
"type": 0,
"name": "query",
"id": 2
}
],
"userId": 1,
"name": "r1",
"id": 11
},
{
"permissionList": [
{
"type": 0,
"name": "del",
"id": 3
}
],
"userId": 1,
"name": "r2",
"id": 12
}
],
"password": "123456",
"id": 1,
"username": "a1"
},
{
"sysRole": {
"userId": 2,
"name": "r3",
"id": 13
},
"nickName": "aa22",
"roleList": [
{
"userId": 2,
"name": "r3",
"id": 13
}
],
"password": "123456",
"id": 2,
"username": "a2"
}
]
page
{
"optimizeCountSql": true,
"records": [
{
"nickName": "aa11",
"roleList": [
{
"permissionList": [
{
"type": 0,
"name": "add",
"id": 1
},
{
"type": 0,
"name": "query",
"id": 2
}
],
"userId": 1,
"name": "r1",
"id": 11
},
{
"permissionList": [
{
"type": 0,
"name": "del",
"id": 3
}
],
"userId": 1,
"name": "r2",
"id": 12
}
],
"password": "123456",
"id": 1,
"username": "a1"
},
{
"sysRole": {
"userId": 2,
"name": "r3",
"id": 13
},
"nickName": "aa22",
"roleList": [
{
"userId": 2,
"name": "r3",
"id": 13
}
],
"password": "123456",
"id": 2,
"username": "a2"
}
],
"searchCount": true,
"total": 0,
"current": 1,
"size": 10,
"orders": [
]
}
到此这篇关于使用AOP+反射实现自定义Mybatis多表关联的文章就介绍到这了,更多相关Mybatis多表关联内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341