当前位置:   article > 正文

除了做分页,Mybatis插件机制还能这么玩

除了做分页,Mybatis插件机制还能这么玩

背景:项目中使用Mybatis做持久层,数据库表设计上存在很多表存在共同的字段,比如创建/更新者、创建/修建时间字段。

做法:对于表映射的实体类,可以将相同的字段抽离到父类(抽象或者普通类),但需要维护这些字段数据的插入/修改。

问题:如何更新/维护这些共同的字段?如果一次操作涉及到多张存在相同字段的表又该如何做?

DEMO技术栈:SpringBoot + Mybatis + H2 + SpringSecurity

  • SpringBoot 主要利用spring-boot-starter-web来实现测试接口
  • Mybatis 项目用到的持久层方案,本文主要讨论以Mybatis基于Executor类型的拦截原理展开(纯注解版)
  • H2 用于测试的内存数据库
  • SpringSecurity 用于通过spring security上下文获取登陆人的id,本文不是以spring security为主,简单集成

思路分析与讨论

  • 对于数据库表跟Dao/Mapper来讲是一一对应的;Service层面的一次操作,如果涉及到多张存在相同字段的表操作,需要一次性同时更新多张表的更新字段(人/时间)
  • 直接在Service做AOP拦截的,是基于整个方法的拦截,达不到维护相同字段的需求
  • 直接在mapper(xml或注解中维护)的方式,不得不自己写代码维护,维护相同字段的逻辑会遍布你的代码/xml当中,增大了代码维护度;即便这么做也很难做到通用化。
  • 回归标题:只能回到Mybatis的底层基于插件机制的拦截(分页插件也是基于此原理,只是拦截类型不一样;其他方案还有MybatisPlus)
走起!贴代码!!(gradle方式)
  • 项目依赖(SpringBoot/Security2.6.1+Mybatis2.2.2)
buildscript {
    ext {
        springBootVersion = '2.6.1'
    }
    repositories {
        maven { url 'https://maven.aliyun.com/repository/public/'}
        mavenLocal()
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'//spring-boot-starter依赖管理


sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies{
    implementation "org.springframework.boot:spring-boot-starter-web"
    implementation "org.springframework.boot:spring-boot-starter-security"
    implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2")
    runtimeOnly "com.h2database:h2"
}
repositories {
    maven { url 'https://maven.aliyun.com/repository/public/'}
    mavenLocal()
    mavenCentral()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 配置应用端口、应用上下文、初始化H2 ddl文件、Mybatis字段驼峰映射
server:
  port: 8888
  servlet.context-path: /demo
spring:
  datasource:
    schema: classpath:ddl.sql
mybatis:
  configuration:
    map-underscore-to-camel-case: true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
create table primary_entity ( id integer primary key auto_increment,name varchar,create_time timestamp,created_by varchar,update_time timestamp,updated_by varchar);
insert into primary_entity(name,create_time,created_by,update_time,updated_by)values('hello demo name',CURRENT_TIMESTAMP(),'admin',CURRENT_TIMESTAMP(),'admin');

create table secondary_entity (id integer primary key auto_increment,desc varchar,create_time timestamp,created_by varchar,update_time timestamp,updated_by varchar);
insert into secondary_entity(desc,create_time,created_by,update_time,updated_by)values('hello demo desc',CURRENT_TIMESTAMP(),'admin',CURRENT_TIMESTAMP(),'admin');
* 定义模型实体类BasicEntity、PrimaryEntity、SecondaryEntity(BasicEntity包含公共维护字段,PrimaryEntity、SecondaryEntity继承BasicEntity)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • SpringSecurity简单配置,写死一个账号,只是为了模拟维护createdBy/updatedBy公共字段时,从SecurityContext获取登录用户id
public class BasicEntity {
    private String createdBy;
    private String updatedBy;
    private Date createTime;
    private Date updateTime;
}
public class PrimaryEntity extends BasicEntity {
        private Integer id;
        private String name;
}
public class SecondaryEntity extends BasicEntity {
        private Integer id;
        private String desc;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 定义DAO/Mapper层,PrimaryMapper、SecondaryMapper
@Mapper
public interface PrimaryMapper {
    @Insert({"insert into primary_entity(name)values(#{name})"})
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(PrimaryEntity primaryEntity);

    @Select("select * from primary_entity where primary_entity.id=#{id}")
    PrimaryEntity find(@Param("id") Integer id);

    @Update("update primary_entity set name=#{name} where primary_entity.id=#{id}")
    int update(PrimaryEntity primaryEntity);
}
@Mapper
public interface SecondaryMapper {
    @Insert({"insert into secondary_entity(desc)values(#{desc})"})
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(SecondaryEntity secondaryEntity);

    @Select("select * from secondary_entity where secondary_entity.id=#{id}")
    SecondaryEntity find(@Param("id") Integer id);

    @Update("update secondary_entity set desc=#{desc} where secondary_entity.id=#{id}")
    int update(SecondaryEntity secondaryEntity);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 定义返回值VO
public class BothEntitiesVo {
    private PrimaryEntity primaryEntity;
    private SecondaryEntity secondaryEntity;
}
  • 1
  • 2
  • 3
  • 4
  • 定义Service层
public interface DemoService {
	//向PrimaryEntity、SecondaryEntity表插入数据,content分别存储于PrimaryEntity的name字段、SecondaryEntity的desc字段
	//返回 包含PrimaryEntity和SecondaryEntity的BothEntitiesVo (测试新增数据后,createdBy,createTime的值是否插入数据库)
    BothEntitiesVo insert(String content);
    //返回 包含PrimaryEntity和SecondaryEntity的BothEntitiesVo (测试修改数据后,updatedBy,updateTime的值是否插入数据库)
    BothEntitiesVo update(Integer pid,Integer sid,String content);
}
@Service
public class DemoServiceImpl implements DemoService{

    @Autowired
    PrimaryMapper primaryMapper;
    @Autowired
    SecondaryMapper secondaryMapper;

    @Override
    public BothEntitiesVo insert(String content) {
        PrimaryEntity primaryEntity = new PrimaryEntity();
        primaryEntity.setName("NAME==".concat(content));//将content存入PrimaryEntity的name字段
        primaryMapper.insert(primaryEntity);
        SecondaryEntity secondaryEntity = new SecondaryEntity();
        secondaryEntity.setDesc("DESC=".concat(content));//将content存入SecondaryEntity的desc字段
        secondaryMapper.insert(secondaryEntity);
        return new BothEntitiesVo(primaryEntity,secondaryEntity);
    }

    @Override
    public BothEntitiesVo update(Integer pid,Integer sid,String content) {
        PrimaryEntity primaryEntity = primaryMapper.find(pid);
        primaryEntity.setName("name=".concat(content));//修改PrimaryEntity的name字段为content
        primaryMapper.update(primaryEntity);
        SecondaryEntity secondaryEntity = secondaryMapper.find(sid);
        secondaryEntity.setDesc("desc=".concat(content));//修改SecondaryEntity的desc字段为content
        secondaryMapper.update(secondaryEntity);
        return new BothEntitiesVo(primaryEntity,secondaryEntity);
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 【核心】定义拦截类CommonInterceptor,再对数据库操作前,对实体表的公共字段进行维护
@Component
@Intercepts({
        @Signature(type=Executor.class,method="update",args={MappedStatement.class,Object.class}),
})
public class CommonInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0];
        Object object = invocation.getArgs()[1];
        if(object instanceof BasicEntity){
            BasicEntity basiceEntity = (BasicEntity)object;
            Date date = new Date();
            //从SpringSecurity上下文获取登录者
            String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();
            //如果是插入数据,更新创建者与创建时间
            if(mappedStatement.getSqlCommandType() == SqlCommandType.INSERT){
                basiceEntity.setCreatedBy(currentUser);
                basiceEntity.setCreateTime(date);
            }
            // 更新修改者与修改时间
            basiceEntity.setUpdatedBy(currentUser);
            basiceEntity.setUpdateTime(date);
        }
        return invocation.proceed();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 定义接口层DemoController
@RestController
public class DemoController {

    @Autowired
    DemoService demoService;

    @GetMapping("/insert/{content}")
    public BothEntitiesVo insert(@PathVariable(value = "content")String content){
        return demoService.insert(Objects.isNull(content)?"TEST":content);
    }

    @GetMapping("/update/{pid}/{sid}/{content}")
    public BothEntitiesVo update(
            @PathVariable(value = "pid")Integer pid,
            @PathVariable(value = "sid")Integer sid,
            @PathVariable(value = "content")String content
    ){
        return demoService.update(pid,sid,Objects.isNull(content)?"TEST":content);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 测试,直接更新ddl文件中创建的PrimaryEntity(id=1)和SecondaryEntity(id=1)
修改测试
浏览器访问 http://127.0.0.1:8888/demo/update/1/1/efg
使用john/123456 进行表单登录
结果如下(name/desc被修改成带efg,注意 创建者是ddl写死的admin,修改人是john):
{"primaryEntity":{"createdBy":"admin","updatedBy":"john","createTime":"2022-03-13T13:02:49.737+00:00","updateTime":"2022-03-13T13:03:28.083+00:00","id":1,"name":"name=efg"},"secondaryEntity":{"createdBy":"admin","updatedBy":"john","createTime":"2022-03-13T13:02:49.762+00:00","updateTime":"2022-03-13T13:03:28.090+00:00","id":1,"desc":"desc=efg"}}
  • 1
  • 2
  • 3
  • 4
  • 5
新增测试
浏览器访问 http://127.0.0.1:8888/demo/insert/abcefghijklmnopqrstuvwxyz
使用john/123456 进行表单登录(如果登陆过了,忽略)
结果如下(name/desc被修改成带abcefghijklmnopqrstuvwxyz,注意创建者和修改人都是john):
{"primaryEntity":{"createdBy":"john","updatedBy":"john","createTime":"2022-03-13T13:06:15.673+00:00","updateTime":"2022-03-13T13:06:15.673+00:00","id":2,"name":"NAME==abcefghijklmnopqrstuvwxyz"},"secondaryEntity":{"createdBy":"john","updatedBy":"john","createTime":"2022-03-13T13:06:15.679+00:00","updateTime":"2022-03-13T13:06:15.679+00:00","id":2,"desc":"DESC=abcefghijklmnopqrstuvwxyz"}}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/658180
推荐阅读
相关标签
  

闽ICP备14008679号