文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

怎么使用AOP+反射实现自定义Mybatis多表关联查询

2023-06-30 16:31

关注

这篇“怎么使用AOP+反射实现自定义Mybatis多表关联查询”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“怎么使用AOP+反射实现自定义Mybatis多表关联查询”文章吧。

一、需求

目前使用的ORM框架是Mybatis Plus,是Mybatis的增强框架,基础的CRUD的方法都集成了,开发起来很是方便。但是项目中总是需要多表关联查询。

Mybatis的多表关联有两种

一、在Mapper中使用@Result @One @Many注解

二、在xml文件中配置对应的resultMap和关联标签

使用起来很不方便。JPA倒是有多表关联的注解实现,但是不想再引入另一个ORM框架。

目前的需求是增强现有的查询,使用简单的注解即可实现多表关联。

二、核心代码

实现该功能总共需要四个文件

怎么使用AOP+反射实现自定义Mybatis多表关联查询

两个自定义注解,一个虚拟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;@Mapperpublic 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;@Mapperpublic 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@Aspectpublic 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;@Servicepublic 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()));    }}

测试数据

怎么使用AOP+反射实现自定义Mybatis多表关联查询

怎么使用AOP+反射实现自定义Mybatis多表关联查询

怎么使用AOP+反射实现自定义Mybatis多表关联查询

怎么使用AOP+反射实现自定义Mybatis多表关联查询

测试结果

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多表关联查询”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注编程网行业资讯频道。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯