我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

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

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

使用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

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

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

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

MyBatis怎么实现自定义映射关系和关联查询

本篇内容介绍了“MyBatis怎么实现自定义映射关系和关联查询”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、使用注解实现自定义映射关系当
2023-07-06

MyBatis自定义映射关系和关联查询实现方法详解

这篇文章主要介绍了MyBatis自定义映射关系和关联查询实现方法,当POJO属性名与数据库列名不一致时,需要自定义实体类和结果集的映射关系,在MyBatis注解开发中,使用@Results定义并使用自定义映射,使用@ResultMap使用自定义映射
2023-05-15

MyBatis注解开发之实现自定义映射关系和关联查询

本文主要详细介绍了MyBatis注解开发中,实现自定义映射关系和关联查询,文中有详细的代码示例,对学习MyBatis有一定的参考价值,需要的朋友可以参考阅读
2023-05-15

JPA怎么使用nativequery多表关联查询返回自定义实体类

这篇文章主要介绍了JPA怎么使用nativequery多表关联查询返回自定义实体类,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。JPA nativequery多表关联查询返回
2023-06-25

使用Mybatis 如何实现多对一或一对多的关联查询

使用Mybatis 如何实现多对一或一对多的关联查询?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。首先 数据库量表之间字段关系(没有主外键)studentmajor表的i
2023-05-31

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录