搭建基于mybatis-plus的开发框架
一、framework-mysql模块编写
上节我们展示了SpringBoot下常见的4种ORM,对于一般性的公司业务,小编认为mybatis-plus更为适合,下面,就让我们一起来搭建基于mybatis-plus的开发框架吧。
1.1 mybatis-plus的entity的基类
我们的Entity基类有2个,分别为SysBaseEntity和BaseEntity
SysBaseEntity一般用于系统生成的数据和子表。比如版本记录、用户操作记录等,就属于系统生成数据。比如某项目的计划有若干子项,项目计划建一张表,子项建一张表。修改了子项就意味着修改了项目计划,删除项目计划那么子项也会无法查找到。
BaseEntity用于由系统内用户创建的,需要记录创建修改者的用户id,用户昵称,用户ip等信息。假删除的字段也放在这里。
@Data
public class SysBaseEntity {
@Schema(title = "主键")
@TableId(type = IdType.ASSIGN_ID)
protected Long id;
@JSONField(serialize = false, deserialize = false)
@TableField(fill = FieldFill.INSERT)
@Schema(title = "创建时间")
protected LocalDateTime createTime;
@JSONField(serialize = false, deserialize = false)
@TableField(fill = FieldFill.UPDATE)
@Schema(title = "修改时间")
protected LocalDateTime modifyTime;
@Schema(title = "版本")
@Version
protected Integer version;
public void createInit() {
id = IdWorker.getId();
// 靠全局配置填充
createTime = null;
modifyTime = null;
version = 0;
}
public void updateInit() {
modifyTime = null;
}
}
@Data
public class BaseEntity extends SysBaseEntity{
@JSONField(serialize = false, deserialize = false)
@TableField(fill = FieldFill.INSERT)
@Schema(title = "创建用户Id")
protected Long createBy;
@JSONField(serialize = false, deserialize = false)
@TableField(fill = FieldFill.INSERT)
@Schema(title = "创建用户昵称")
protected String createName;
@JSONField(serialize = false, deserialize = false)
@TableField(fill = FieldFill.INSERT)
@Schema(title = "创建者Ip")
protected String createIp;
@JSONField(serialize = false, deserialize = false)
@TableField(fill = FieldFill.UPDATE)
@Schema(title = "修改用户Id")
protected Long modifyBy;
@JSONField(serialize = false, deserialize = false)
@TableField(fill = FieldFill.UPDATE)
@Schema(title = "修改用户昵称")
protected String modifyName;
@JSONField(serialize = false, deserialize = false)
@TableField(fill = FieldFill.UPDATE)
@Schema(title = "创建者Ip")
protected String modifyIp;
@JSONField(serialize = false, deserialize = false)
@TableLogic(delval = "id",value = "0")
@Schema(title = "是否删除")
protected Long del;
@Override
public void createInit() {
super.createInit();
// 靠全局配置填充
createBy = null;
createName = null;
createIp = null;
modifyBy = null;
modifyName = null;
modifyIp = null;
version = 0;
del = 0L;
}
@Override
public void updateInit() {
super.updateInit();
modifyBy = null;
modifyName = null;
modifyIp = null;
}
}
1.2 SpringBoot下的Dto、Po、VO转换
通常,我们把Java类与数据库表的对应叫Po;把前端传向后端,后端controller的接受类叫Dto;把通过Controller返回到前端的类叫Vo。
为方便编码,我们写Vo时,会让Vo继承自Po;如果想隐藏字段,直接在Po中的相应字段添加@JSONField(serialize = false, deserialize = false)注解;如果想增加字段,直接在Vo中添加。
Spring框架下有很多Bean转换的工具类,但我用起来,最舒适的还是dozer的库(虽然他的执行效率不是最高的)。原因在于他的接口可以直接帮程序员new出一个转换的类,节省了一行没有意义的代码。
工具类分两个,一个是基类的DtoEntityUtil转换,放在framework-common模块中;一个是DbDtoEntityUtil,放在framework-mysql模块中;
DbDtoEntityUtil相比基类,添加了2个dtoToPo的接口,createFromDto用于实体的创建,editByDto用于实体的更新。
下面分别展示两个类的代码:
public class DtoEntityUtil {
protected static DozerBeanMapper mapper;
public static void init(){
mapper = new DozerBeanMapper();
mapper.setMappingFiles(Collections.singletonList("dozerJdk8Converters.xml"));
}
public static <D, E> E trans(D t, Class<E> clazz) {
if (t == null) {
return null;
}
return mapper.map(t, clazz);
}
public static <D, E> List<E> trans(D[] ts, Class<E> clazz) {
List<E> es = new ArrayList<E>();
if (ts == null) {
return es;
}
for (D d : ts) {
E e = (E) trans(d, clazz);
if (e != null) {
es.add(e);
}
}
return es;
}
public static <D, E> List<E> trans(List<D> ts, Class<E> clazz) {
List<E> es = new ArrayList<E>();
if (ts == null) {
return es;
}
for (D d : ts) {
E e = (E) trans(d, clazz);
if (e != null) {
es.add(e);
}
}
return es;
}
public static <T> void copy(T dst, T src){
mapper.map(src, dst);
}
}
public class DbDtoEntityUtil extends DtoEntityUtil {
/**
* 用于新增
*
* @param pDto dto实体
* @param clazz po的class
* @return 初始化好的po
*/
public static <D, E> E createFromDto(D pDto, Class<E> clazz) {
if (pDto == null) {
return null;
}
E result = mapper.map(pDto, clazz);
if(result instanceof SysBaseEntity){
((SysBaseEntity) result).createInit();
}
return result;
}
/**
* 用于更新
*
* @param pPo 待更新的po实体
* @param pDto dto实体
* @param pPoClass po的类
* @return 更新后的po
*/
public static <D, E> E editByDto(E pPo, D pDto, Class<E> pPoClass) {
if (pDto == null) {
return null;
}
E result = mapper.map(pPo, pPoClass);
copy(result,pDto);
if(result instanceof SysBaseEntity){
((SysBaseEntity) result).updateInit();
}
return result;
}
}
1.3 mybatis-plus的通用的service
mybatis-plus的通用service虽然很好用,但并不能完全满足我们的需求。在实际开发中,我们通常会自己写一个通用service继承自mybatis-plus的ServiceImpl。下面将展示代码:
@Service
public @interface ZfDbService{
String name();
}
public interface IZfDbService<T> extends IService<T> {
<VO>Page<VO> page(IPage page, Wrapper<T> queryWrapper, Class<VO> cls);
<VO>Page<VO> page(IPage page, Wrapper<T> queryWrapper, Class<VO> cls, Function<T,VO> convertor);
<VO> List<VO> list(Wrapper<T> queryWrapper, Class<VO> cls);
<VO> List<VO> list(Wrapper<T> queryWrapper, Class<VO> cls, Function<T,VO> convertor);
T findOne(Wrapper<T> queryWrapper);
boolean exist(Wrapper<T> queryWrapper);
T check(Serializable pId);
}
public class ZfDbServiceImpl<M extends BaseMapper<T>, T> extends ServiceImpl<M, T> implements IZfDbService<T> {
@Override
public <VO> Page<VO> page(IPage page, Wrapper<T> queryWrapper, Class<VO> cls) {
IPage<T> pageData = super.page(page,queryWrapper);
Page<VO> pageVoData = new Page<VO>(pageData.getCurrent(),pageData.getSize());
pageVoData.setTotal(pageData.getTotal());
pageVoData.setPages(pageData.getPages());
List<VO> voList = DtoEntityUtil.trans(pageData.getRecords(),cls);
pageVoData.setRecords(voList);
return pageVoData;
}
@Override
public <VO> Page<VO> page(IPage page, Wrapper<T> queryWrapper, Class<VO> cls, Function<T, VO> convertor) {
IPage<T> pageData = super.page(page,queryWrapper);
Page<VO> pageVoData = new Page<VO>(pageData.getCurrent(),pageData.getSize());
pageVoData.setTotal(pageData.getTotal());
pageVoData.setPages(pageData.getPages());
List<VO> voList = new ArrayList<>(pageData.getRecords().size());
for(T data : pageData.getRecords()){
VO voData = convertor.apply(data);
voList.add(voData);
}
pageVoData.setRecords(voList);
return pageVoData;
}
@Override
public <VO> List<VO> list(Wrapper<T> queryWrapper, Class<VO> cls) {
List<T> listData = super.list(queryWrapper);
List<VO> listVoData = DtoEntityUtil.trans(listData,cls);
return listVoData;
}
@Override
public <VO> List<VO> list(Wrapper<T> queryWrapper, Class<VO> cls, Function<T, VO> convertor) {
List<T> listData = super.list(queryWrapper);
List<VO> listVoData = new ArrayList<>();
for(T data : listData){
VO voData = convertor.apply(data);
listVoData.add(voData);
}
return listVoData;
}
@Override
public T findOne(Wrapper<T> queryWrapper) {
List<T> listData = super.list(queryWrapper);
if(CollectionUtils.isEmpty(listData)){
return null;
}
return listData.get(0);
}
@Override
public boolean exist(Wrapper<T> queryWrapper) {
Long cnt = baseMapper.selectCount(queryWrapper);
return cnt > 0;
}
@Override
public T check(Serializable pId) {
T entity = baseMapper.selectById(pId);
if(null == entity){
throw new ServiceException("没有找到Id为"+pId+"的"+getEntityName()+"数据");
}
return entity;
}
protected String getEntityName(){
ZfDbService zfDbService = this.getClass().getAnnotation(ZfDbService.class);
if(null != zfDbService){
return zfDbService.name();
}else {
return "service";
}
}
}
1.4 mybatis-plus的自动填充
在躺平攻略(2)中,我们已经展示了mybatis-plus的自动填充,我们在这里使用模板模式写一个基类,使业务系统编写时更方便。
@Slf4j
public abstract class BaseMetaObjectHandler implements MetaObjectHandler {
protected static final Long SYS_ID = 0L;
protected static final String SYS_NAME = "sys";
protected static final String SYS_IP = "localhost";
protected String createTimeStr = "createTime";
protected String createByStr= "createBy";
protected String createByNameStr = "createName";
protected String createIpStr = "createIp";
protected String modifyTimeStr = "modifyTime";
protected String modifyByStr= "modifyBy";
protected String modifyByNameStr = "modifyName";
protected String modifyIpStr = "modifyIp";
protected boolean checkFieldNull(MetaObject pMetaObject, String pField){
if(pMetaObject.hasSetter(modifyTimeStr)){
Object orgModifyTime = pMetaObject.getValue(modifyTimeStr);
if(null == orgModifyTime){
return true;
}
}
return false;
}
protected void insertCreateTime(MetaObject pMetaObject){
if(checkFieldNull(pMetaObject,createTimeStr)){
this.strictInsertFill(pMetaObject,createTimeStr,()-> LocalDateTime.now(),LocalDateTime.class);
}
}
protected abstract void insertCreateBy(MetaObject pMetaObject);
protected abstract void insertCreateByName(MetaObject pMetaObject);
protected abstract void insertCreateByIp(MetaObject pMetaObject);
abstract protected void otherInsertFill(MetaObject pMetaObject);
abstract protected boolean hasTokenObject();
@Override
public void insertFill(MetaObject pMetaObject) {
insertCreateTime(pMetaObject);
if(hasTokenObject()){
try{
if(checkFieldNull(pMetaObject,createByStr)){
insertCreateBy(pMetaObject);
}
if(checkFieldNull(pMetaObject,createByNameStr)){
insertCreateByName(pMetaObject);
}
if(checkFieldNull(pMetaObject,createIpStr)){
insertCreateByIp(pMetaObject);
}
}catch (Exception ex){
log.error("insertFill 出现异常",ex);
}
}else{
if(checkFieldNull(pMetaObject,createByStr)){
this.strictInsertFill(pMetaObject,createByStr,()->SYS_ID,Long.class);
}
if(checkFieldNull(pMetaObject,createByNameStr)){
this.strictInsertFill(pMetaObject,createByNameStr,()->SYS_NAME,String.class);
}
if(checkFieldNull(pMetaObject,createIpStr)){
this.strictInsertFill(pMetaObject,createIpStr,()->SYS_IP,String.class);
}
}
otherInsertFill(pMetaObject);
}
protected void updateModifyTime(MetaObject pMetaObject){
if(checkFieldNull(pMetaObject,modifyTimeStr)){
this.strictUpdateFill(pMetaObject,modifyTimeStr,()-> LocalDateTime.now(),LocalDateTime.class);
}
}
protected abstract void updateModifyBy(MetaObject pMetaObject);
protected abstract void updateModifyByName(MetaObject pMetaObject);
protected abstract void updateModifyByIp(MetaObject pMetaObject);
abstract protected void otherUpdateFill(MetaObject pMetaObject);
@Override
public void updateFill(MetaObject pMetaObject) {
updateModifyTime(pMetaObject);
if(hasTokenObject()){
try{
if(checkFieldNull(pMetaObject,modifyByStr)){
updateModifyBy(pMetaObject);
}
if(checkFieldNull(pMetaObject,modifyByNameStr)){
updateModifyByName(pMetaObject);
}
if(checkFieldNull(pMetaObject,modifyIpStr)){
updateModifyByIp(pMetaObject);
}
}catch (Exception ex){
log.error("updateFill 出现异常",ex);
}
}else{
if(checkFieldNull(pMetaObject,modifyByStr)){
this.strictUpdateFill(pMetaObject,modifyByStr,()->SYS_ID,Long.class);
}
if(checkFieldNull(pMetaObject,modifyByNameStr)){
this.strictUpdateFill(pMetaObject,modifyByNameStr,()->SYS_NAME,String.class);
}
if(checkFieldNull(pMetaObject,modifyIpStr)){
this.strictUpdateFill(pMetaObject,modifyIpStr,()->SYS_IP,String.class);
}
}
otherUpdateFill(pMetaObject);
}
}
public class BaseMetaObjectHandlerAdapter extends BaseMetaObjectHandler{
@Override
protected void insertCreateBy(MetaObject pMetaObject) {}
@Override
protected void insertCreateByName(MetaObject pMetaObject) {}
@Override
protected void insertCreateByIp(MetaObject pMetaObject) {}
@Override
protected void otherInsertFill(MetaObject pMetaObject) {}
@Override
protected boolean hasTokenObject() {
return false;
}
@Override
protected void updateModifyBy(MetaObject pMetaObject) {}
@Override
protected void updateModifyByName(MetaObject pMetaObject) {}
@Override
protected void updateModifyByIp(MetaObject pMetaObject) {}
@Override
protected void otherUpdateFill(MetaObject pMetaObject) {}
}
1.5 mybatis-plus的枚举实现与相关配置
对于mybatis-plus的枚举实现,官网有所介绍。
我们这里还是才用方法1,在枚举的code字段上增加@EnumValue注解
mybatis-plus的相关配置很简单:
@AllArgsConstructor
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页
PaginationInnerInterceptor mysqlInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
interceptor.addInnerInterceptor(mysqlInnerInterceptor);
// 乐观锁
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
在yml中,做如下配置:
mybatis-plus:
typeEnumsPackage: indi.zhifa.recipe.bailan.busy.enums
二、应用代码编写和规范
2.1 实体类编写
@Data
@TableName("表名")
@Schema(title = "对象", description = "描述")
public class 主表Entity extends BaseEntity {
@Schema(title = "字段描述")
private 变量类型 变量;
}
一般来说,数据库表字段用小写+下划线模式,变量名用驼峰模式。如果变量名与数据库关键字段重名,可以添加这样的注解
@TableField(value = "`round`")
2.2 继承BaseMetaObjectHandler
应用服务需要写一个类继承BaseMetaObjectHandler,来处理审计字段自动填充。
由于我们还没有讲到鉴权,直接继承BaseMetaObjectHandlerAdapter即可
@Component
public class MetaObjectHandler extends BaseMetaObjectHandlerAdapter {
}
2.3 使用脚手架提供的DBService
有了我们自己写的脚手架,在使用mybatis-plus的通用service时,可以继承我们的IZfDbService,减少重复代码。
2.4 代码目录与分层
小编习惯的目录结构是这样的
- controller 控制器
- api restful接口
- view 用于服务端渲染视图接口
- init 用于服务器的初始化
- aop 用于aop
- schedule 用于定时任务
- dao
- dbservice 用于mybatis-plus的通用接口
- mapper 用于mybatis-plus的mapper
- entity 实体
- annotation 注解
- enums 枚举
- po 数据库实体
- dto 接受前端的实体
- vo 返回前端的实体
- datastruct 通用数据结构
- facade 门面类,按模块分包,一个门面可注入多个dao层的类
- handler 一些处理器
- util 工具类
- memo 缓存
分层开发 controller -> facade -> dao.dbservice->dao.mapper。表层类可以注入底层类,同层类原则上不能相互注入。
2.5 常见的实体接口
任何一个主表的facade,都可以无脑的写这几个接口。
- 按id查询info
实体VO info(Long pId)
- 分页查询
Page<实体VO> page(int pCurrent, int pSize,... 各查询条件)
- 创建实体
实体VO create(实体创建Dto pDto)
- 修改实体
实体VO edit(Long pId, 实体修改Dto pDto)
- 删除实体
boolean edit(Long pId)
2.5 连表操作
在实际开发中,常常会遇到连表操作。首先,我们来构想一个简单的demo业务。
假定我们做个选课系统,有课程和老师两个实体。课程包括id,name,description,lesson_period,score, teacher_id;老师包括id,name,description,age,gender,telephone,email。
为方便起见,这里直接把实体代码给出
@RequiredArgsConstructor
public enum EGender {
MALE(1,"男"),
FEMALE(2,"女");
@EnumValue
@Getter
final Integer code;
@Getter
final String name;
}
@Data
@TableName(value = "sch_teacher")
public class TeacherEntity extends BaseEntity{
@TableId
@Schema(title = "主键")
Long id;
@Schema(title = "教师名字")
String name;
@Schema(title = "性别")
EGender gender;
@Schema(title = "出生日期")
LocalDate birth;
@Schema(title = "电话")
String telephone;
@Schema(title = "邮箱")
String email;
@Schema(title = "年龄")
public Integer getAge(){
if(null != birth){
return birth.until(LocalDate.now()).getYears();
}
return -1;
}
}
@Data
@TableName(value = "sch_course")
public class CourseEntity extends BaseEntity{
@TableId
@Schema(title = "主键")
Long id;
@Schema(title = "课程名称")
String name;
@Schema(title = "课程描述")
String description;
@Schema(title = "课时")
Integer lessonPeriod;
@Schema(title = "学分")
Integer score;
@Schema(title = "是否开放")
Boolean open;
@Schema(title = "老师Id")
Long teacherId;
@Schema(title = "老师名字")
String teacherName;
@Schema(title = "老师出生日期")
LocalDate teacherBirth;
@Schema(title = "老师年龄")
public Integer getTeacherAge(){
if(null != teacherBirth){
return teacherBirth.until(LocalDate.now()).getYears();
}
return -1;
}
}
2.5.1 适当冗余
对不太可能发生改变的字段,进行适当冗余,大大有助于提升开发效率。
以上面的例子,老师的名字、性别、生日在创建好后,一般不会更改,所以干脆冗余过来,方便查找,既提高程序执行效率,也使程序员写代码更省力。但要注意,在老师信息的修改接口,要处理这些级联的修改。
2.5.2 使用视图
连表操作一般用于做查询。如果公司领导脑筋不死板的话,建议使用视图,这种方式最方便。
在mabatis-plus中,操作视图和表是一样的。这里不做赘述。
有很多人会质疑执行效率的问题,使用视图相当于查询了2遍数据库。我觉得这点效率的损失可以接受,web开发的瓶颈通常不在这里。
在视图中连表时,查询条件也是可以命中索引的。有兴趣的同学可以使用explain命令自行测试。
2.5.3 查询2次数据库,使用map做关联
为方便理解,这里直接上代码:
@RequiredArgsConstructor
@ZfFacade(name = "SchoolCourse")
public class SchoolCourseFacadeImpl implements ISchoolCourseFacade {
private final ICourseDbService mCourseDbService;
private final ITeacherDbService mTeacherDbService;
@Override
public Page<CourseDetailViewVo> page(Integer pCurrent, Integer pSize,
Boolean pOpen,
Long pId, String pName, Integer pLessonPeriod, Integer pScore,
Long pTeacherId, String pTeacherName, Integer pAgeMin, Integer pAgeMax, EGender pGender) {
if(null == pOpen){
pOpen = true;
}
LambdaQueryWrapper<CourseEntity> wrapper = Wrappers.<CourseEntity>lambdaQuery()
.eq(CourseEntity::getOpen,pOpen)
.eq(null != pId,CourseEntity::getId,pId)
.like(StringUtils.hasText(pName),CourseEntity::getName,pName)
.eq(null != pLessonPeriod,CourseEntity::getLessonPeriod, pLessonPeriod)
.eq(null != pScore,CourseEntity::getScore,pScore)
.eq(null != pTeacherId,CourseEntity::getTeacherId,pTeacherId)
.like(StringUtils.hasText(pTeacherName),CourseEntity::getTeacherName,pTeacherName)
.between(null != pAgeMin && null != pAgeMax,CourseEntity::getTeacherBirth, LocalDate.now().plusYears(pAgeMin),LocalDate.now().plusYears(pAgeMax))
.eq(null != pGender,CourseEntity::getTeacherGender,pGender);
Page<CourseDetailViewVo> pageConfig = new Page<>(pCurrent,pSize);
Page<CourseDetailViewVo> coursePage = mCourseDbService.page(pageConfig,wrapper,CourseDetailViewVo.class);
List<CourseDetailViewVo> courseEntityList = coursePage.getRecords();
List<Long> teacherIds = courseEntityList.stream().filter(course->course.getOpen()).map(CourseDetailViewVo::getTeacherId).distinct().collect(Collectors.toList());
List<TeacherEntity> teacherEntityList = mTeacherDbService.listByIds(teacherIds);
Map<Long,TeacherEntity> teacherEntityMap = new HashMap<>();
for(TeacherEntity teacherEntity : teacherEntityList){
teacherEntityMap.put(teacherEntity.getId(),teacherEntity);
}
for(CourseDetailViewVo courseDetailViewVo : courseEntityList){
Long teacherId = courseDetailViewVo.getTeacherId();
if(null != teacherId && teacherId > 0){
TeacherEntity teacherEntity = teacherEntityMap.get(teacherId);
courseDetailViewVo.setTeacherEmail(teacherEntity.getEmail());
courseDetailViewVo.setTeacherTelephone(teacherEntity.getTelephone());
}
}
return coursePage;
}
}
2.5.4 使用mybatis原生的xml方式
如果查询条件字段分布在两个实体的字段中,又不想使用视图,最简单的方式就是退回到mybatis的xml
2.6 部分更新
在实际工作中,有的操作并不需要全表更新,甚至也不需要全表更新。比如设置课程的授课老师,或者任课老师的基本信息改变。
当然,很多时候即使全量更新,问题也不大,性能的瓶颈通常不在这里。
2.6.1 使用updateWrapper做更新
mybatis-plus可以使用updateWrapper手动设定需要更新的字段。但这种方式有一个弊端,就是自动填充的那一系列功能就此失效。
所以小编认为,这种做法更适合处理一些联动的改变,比如教师基础信息的变化。实例代码在2.6.4中展示
2.6.2 把不想更新的字段设置为null
对于类似给课程分配教师这样的业务,其实直接把原先的课程查出来,把需要变化的字段更改下,再调用updateById接口全量更新一下并没有什么问题。如果担心性能,可以把预估数据量比较大的字段设置为null,比如这里的教师介绍:
@Transactional(rollbackFor = Exception.class)
@Override
public CourseEntity allocateTeacher(Long pCourseId, Long pTeacherId) {
CourseEntity courseEntity = mCourseDbService.check(pCourseId);
TeacherEntity teacherEntity = mTeacherDbService.check(pTeacherId);
courseEntity.updateInit();
courseEntity.setTeacherId(teacherEntity.getId());
courseEntity.setTeacherName(teacherEntity.getName());
courseEntity.setTeacherBirth(teacherEntity.getBirth());
courseEntity.setDescription(null);
mCourseDbService.updateById(courseEntity);
return courseEntity;
}
2.6.3 把一个表拆分成多个Java实体
如果是在介意这种做法,可以把一个表分成多个实体操作,但同时意味着需要手动写多分mapper,service……
2.6.3 使用Spring框架下的消息处理冗余字段更新
Spring框架有个简单的消息系统,该功能十分适合处理冗余字段更新,废话不多说,直接上代码吧:
@RequiredArgsConstructor
public enum EAppEventType {
TEACHER_INFO_CHANGE(1,"老师信息更改");
@EnumValue
@Getter
final Integer code;
@Getter
final String name;
}
public class AppEvent extends ApplicationEvent {
@Getter
private final EAppEventType appEventType;
@Getter
private final Long targetId;
@Getter
private final Object param;
public AppEvent(EAppEventType pAppEventType, Long pTargetId, Object pParam, Object pSource){
super(pSource);
appEventType = pAppEventType;
targetId = pTargetId;
param = pParam;
}
}
@Data
public class TeacherCreateDto {
@Schema(title = "教师名字")
String name;
@Schema(title = "性别")
EGender gender;
@Schema(title = "出生日期")
LocalDate birth;
@Schema(title = "电话")
String telephone;
@Schema(title = "邮箱")
String email;
}
@Data
public class TeacherEditDto {
@Schema(title = "教师名字")
String name;
@Schema(title = "性别")
EGender gender;
@Schema(title = "出生日期")
LocalDate birth;
@Schema(title = "电话")
String telephone;
@Schema(title = "邮箱")
String email;
}
@RequiredArgsConstructor
@ZfFacade(name = "TeacherFacade")
public class TeacherFacadeImpl implements ITeacherFacade {
private final ITeacherDbService mTeacherDbService;
private final ApplicationContext mApplicationContext;
@Override
public TeacherEntity create(TeacherCreateDto pCreateConfig) {
TeacherEntity teacherEntity = DbDtoEntityUtil.createFromDto(pCreateConfig,TeacherEntity.class);
mTeacherDbService.save(teacherEntity);
return teacherEntity;
}
@Transactional(rollbackFor = Exception.class)
@Override
public TeacherEntity edit(Long pId, TeacherEditDto pTeacherEditDto) {
TeacherEntity orgTeacherEntity = mTeacherDbService.check(pId);
TeacherEntity teacherEntity = DbDtoEntityUtil.editByDto(orgTeacherEntity,pTeacherEditDto,TeacherEntity.class);
mTeacherDbService.updateById(teacherEntity);
AppEvent appEvent = new AppEvent(EAppEventType.TEACHER_INFO_CHANGE,teacherEntity.getId(),null,teacherEntity);
mApplicationContext.publishEvent(appEvent);
return teacherEntity;
}
}
@RequiredArgsConstructor
@ZfFacade(name = "SchoolCourse")
public class CourseFacadeImpl implements ICourseFacade , ApplicationListener<AppEvent> {
private final ICourseDbService mCourseDbService;
private final ITeacherDbService mTeacherDbService;
@Transactional(rollbackFor = Exception.class)
@Override
public CourseEntity allocateTeacher(Long pCourseId, Long pTeacherId) {
CourseEntity courseEntity = mCourseDbService.check(pCourseId);
TeacherEntity teacherEntity = mTeacherDbService.check(pTeacherId);
courseEntity.updateInit();
courseEntity.setTeacherId(teacherEntity.getId());
courseEntity.setTeacherName(teacherEntity.getName());
courseEntity.setTeacherBirth(teacherEntity.getBirth());
mCourseDbService.updateById(courseEntity);
return courseEntity;
}
private void updateForTeacherInfoChange(TeacherEntity pTeacherEntity){
LambdaQueryWrapper<CourseEntity> wrapper = Wrappers.<CourseEntity>lambdaQuery()
.eq(CourseEntity::getTeacherId,pTeacherEntity.getId());
List<CourseEntity> courseEntityList = mCourseDbService.list(wrapper);
for(CourseEntity courseEntity : courseEntityList){
courseEntity.setTeacherName(pTeacherEntity.getName());
courseEntity.setTeacherBirth(pTeacherEntity.getBirth());
courseEntity.setTeacherGender(pTeacherEntity.getGender());
}
mCourseDbService.updateBatchById(courseEntityList);
}
@Override
public void onApplicationEvent(AppEvent event) {
switch (event.getAppEventType()) {
case TEACHER_INFO_CHANGE:
updateForTeacherInfoChange((TeacherEntity)event.getSource());
break;
}
}
}