打造一款适合自己的快速开发框架-mapper逻辑删除及枚举类型规范

2,051 阅读6分钟

前言

一般来说,业务系统对表的操作很少会使用物理删除,所以在建表的时候,会默认添加一个是否删除的状态来做逻辑删除。而如果已经删除了的数据,也不会在业务系统上体现,所以需要在每个查询条件上增加未删除这个条件,而如果每次查询都需要手动增加该条件,着实不方便。研究了一下通用mapper,该持久化框架是支持逻辑删除的。

mapper逻辑删除

mapper的逻辑删除配置比较简单,只需要在逻辑删除的字段上增加@LogicDelete注解即可。LogicDelete在包tk.mybatis.mapper.annotation下。

配置样例:

@LogicDelete(isDeletedValue=1,notDeletedValue=2)
private Integer isDeleted;
字段 类型 描述
isDeletedValue int 已删除的值,默认1
notDeletedValue int 未删除的值,默认0

查询样例:

sysUserMapper.selectAll();

查询自动构建的sql:

SELECT id,user_name,real_name,avatar,email,mobile_phone,telephone,password,salt,sex,is_locked,create_time,update_time,is_deleted FROM sys_user WHERE is_deleted = 2 

枚举类型规范

我们做开发的时候经常会遇到这样子的问题:数据库字段某些值是固定的几个值,常规做法是直接存int类型,然后业务代码上定义常量或者枚举类,这种方式其实确实也挺不错的,但是需要人工去维护常量或者枚举类,有点不方便。下面说一下本框架上的做法。

先看表:

CREATE TABLE `sys_user` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_name` varchar(32) NOT NULL COMMENT '用户名',
  `real_name` varchar(32) DEFAULT NULL COMMENT '姓名',
  `avatar` varchar(200) DEFAULT NULL COMMENT '头像',
  `email` varchar(64) DEFAULT NULL COMMENT '邮箱',
  `mobile_phone` varchar(11) DEFAULT NULL COMMENT '手机号',
  `telephone` varchar(20) DEFAULT NULL COMMENT '电话',
  `password` varchar(40) DEFAULT NULL COMMENT '密码',
  `salt` varchar(10) DEFAULT NULL COMMENT '加盐',
  `sex` int(6) unsigned DEFAULT '1' COMMENT '性别(1->男|MALE,2->女|FEMALE,3->未知|UNKNOWN)',
  `is_locked` tinyint(1) unsigned DEFAULT '2' COMMENT '是否锁定(1->已锁定|YES,2->未锁定|NO)',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `is_deleted` tinyint(1) unsigned DEFAULT '1' COMMENT '是否删除(1->未删除|NO,2->已删除|YES)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='用户';

从上表看出有三个字段的注释和别的字段注释不太一样,sex、is_locked、is_deleted。这个注释是我自己制定的规范。这里详细说明一下。

字典(名称、唯一编码)

name key 枚举类
性别 sys_user_sex SysUser.SexEnum
是否锁定 sys_user_is_locked SysUser.IsLockedEnum
是否删除 sys_user_is_deleted SysUser.IsDeletedEnum

注:是否模式可以只定义一个全局的yes_no->YesNoEnum

名称即注释,唯一编码即表名_字段

字典项(名称、唯一编码、值)

以性别为例:

名称 唯一编码
MALE 1
FEMALE 2
未知 UNKNOWN 3

当注释中有了这些元数据后,代码生成器会进行生成代码的处理,后面讲到代码生成器后会详细说明。

开始编码

目录结构

只罗列需要新增或修改的文件

├── mldong-admin  管理端接口
    ├── src/main/java
      └──com.mldong.modules.sys
        └── service 服务层
        	└── impl
        		└── SysUserServiceImpl
    └── src/main/resources
    	└── application.yml
├── mldong-common  工具类及通用代码
├── mldong-generator  代码生成器
└── mldong-mapper 持久层
    ├── src/main/java
    	├── com.mldong.annotation
    		└── DictEnum.java
    	├── com.mldong.base
    		├── CodedEnum.java
    		├── CodedEnumTypeHandle.java
    		└── YesNoEnum.java
    	└── com.mldong.modules.sys.entity
    		└── SysUser.java

文件说明

  • mldong-mapper/src/main/java/com/mldong/annotation/DictEnum.java

定义注解,和之前的ErrEnum一样,为了扫描进内存,方便后续查询和做字典接口的。本次修改的文件应该放在mldong-framework层的,不过现在暂时没多少,所以就暂时放在mldong-mapper层。不放在mldong-common层的原因是后续mldong-common会引入mldong-mapper,模块不能相互依赖。所以。。。。

package com.mldong.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 字典枚举类注解,方便收集做为字典使用的
 * @author mldong
 *
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DictEnum {
	/**
	 * 名称
	 * @return
	 */
	String name();
	/**
	 * 唯一标识
	 * @return
	 */
	String key();
}
  • mldong-mapper/src/main/java/com/mldong/base/CodedEnum.java

字典码接口,并提供value转枚举的方法

package com.mldong.base;

import java.util.Arrays;
import java.util.Optional;

public interface CodedEnum {
    int getValue();
    String getName();
    public static <E extends Enum<?> & CodedEnum> Optional<E> codeOf(Class<E> enumClass, int code) {
        return Arrays.stream(enumClass.getEnumConstants()).filter(e -> e.getValue() == code).findAny();
    }
}

  • mldong-mapper/src/main/java/com/mldong/base/CodedEnumTypeHandle.java
package com.mldong.base;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Optional;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
/**
 * mybatis枚举处理类
 * @author mldong
 *
 * @param <E>
 */
@MappedTypes({CodedEnum.class})
public class CodedEnumTypeHandler<E extends Enum<?> & CodedEnum> extends BaseTypeHandler<E> {
    private Class<E> type;

    public CodedEnumTypeHandler(Class<E> type) {
        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        this.type = type;
    }

	@Override
	public void setNonNullParameter(PreparedStatement ps, int i, E parameter,
			JdbcType jdbcType) throws SQLException {
		if (parameter == null) {
            ps.setNull(i, Types.TINYINT);
        } else {
            ps.setInt(i, parameter.getValue());
        }
	}

	@Override
	public E getNullableResult(ResultSet rs, String columnName)
			throws SQLException {
		int columnValue = rs.getInt(columnName);
        return rs.wasNull() ? null : enumOf(columnValue);
	}

	@Override
	public E getNullableResult(ResultSet rs, int columnIndex)
			throws SQLException {
		int columnValue = rs.getInt(columnIndex);
        return rs.wasNull() ? null : enumOf(columnValue);
	}

	@Override
	public E getNullableResult(CallableStatement cs, int columnIndex)
			throws SQLException {
		int columnValue = cs.getInt(columnIndex);
        return cs.wasNull() ? null : enumOf(columnValue);
	}
    private E enumOf(int code) {
        final Optional<E> codedEnumOpt = CodedEnum.codeOf(type, code);
        if (codedEnumOpt.isPresent()) {
            return codedEnumOpt.get();
        } else {
            throw new IllegalArgumentException("Cannot convert " + code + " to " + type.getSimpleName() + " by code value.");
        }
    }
}
  • mldong-mapper/src/main/java/com/mldong/base/YesNoEnum.java

yes or no模型,对应如:是否删除(1->未删除|NO,2->已删除|YES)

package com.mldong.base;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
/**
 * yes_no
 * @author mldong
 *
 */
public enum YesNoEnum implements CodedEnum{
	/**
	 * 是
	 */
	YES(1, "是"),
	/**
	 * 否
	 */
	NO(2,"否")
	;
	private int value;
	private String name;
	/**
	 * 未删除
	 */
	public final static int Y=1;
	/**
	 * 已删除
	 */
	public final static int N=2;
	@JsonCreator
    public static YesNoEnum forValue(int value) {
        return CodedEnum.codeOf(YesNoEnum.class, value).get();

    }
	YesNoEnum(int value, String name) {
		this.value = value;
		this.name = name;
	}
	@JsonValue
	public int getValue() {
		return value;
	}
	public String getName() {
		return name;
	}
}
  • mldong-mapper/src/main/java/com/mldong/modules/sys/entity/SysUser.java

实体类对应修改

package com.mldong.modules.sys.entity;

import io.swagger.annotations.ApiModelProperty;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Id;
import javax.persistence.Table;

import tk.mybatis.mapper.annotation.LogicDelete;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.mldong.annotation.DictEnum;
import com.mldong.base.CodedEnum;
import com.mldong.base.YesNoEnum;
/**
 * 用户表实体
 * @author mldong
 *
 */
@Table(name="sys_user")
public class SysUser implements Serializable{
	/**
	 * 
	 */
	private static final long serialVersionUID = -2687095050668229447L;
	@Id
	@ApiModelProperty(value="主键")
	private Long id;
	@ApiModelProperty(value="用户名")
	private String userName;
	@ApiModelProperty(value="姓名")
	private String realName;
	@ApiModelProperty(value="头像")
	private String avatar;
	@ApiModelProperty(value="邮箱")
	private String email;
	@ApiModelProperty(value="手机号")
	private String mobilePhone;
	@ApiModelProperty(value="联系电话")
	private String telephone;
	@ApiModelProperty(value="密码")
	private String password;
	@ApiModelProperty(value="加盐")
	private String salt;
	@ApiModelProperty(value="性别")
	private Integer sex;
	@ApiModelProperty(value="是否锁定")
	private YesNoEnum isLocked;
	@ApiModelProperty(value="创建时间")
	private Date createTime;
	@ApiModelProperty(value="更新时间")
	private Date updateTime;
	@LogicDelete(isDeletedValue=YesNoEnum.Y,notDeletedValue=YesNoEnum.N)
	@ApiModelProperty(value="是否删除")
	private YesNoEnum isDeleted;
	// get set 略
	@DictEnum(name="性别", key="sys_user_sex")
	public enum SexEnum implements CodedEnum {
		
		/**
		 * 男
		 */
		MALE(1, "男"),
		/**
		 * 女
		 */
		FEMALE(2, "女"),
		/**
		 * 未知
		 */
		UNKNOWN(3, "未知"),
		;
		private int value;
		private String name;
		@JsonCreator // str->json的时候,由value->enums
	    public static SexEnum forValue(int value) {
	        return CodedEnum.codeOf(SexEnum.class, value).get();

	    }
		SexEnum(int value, String name) {
			this.value = value;
			this.name = name;
		}
		@JsonValue // json->str的时候,enums->value
		public int getValue() {
			return value;
		}
		public String getName() {
			return name;
		}
    }
}

  • mldong-admin/src/main/java/com/mldong/modules/sys/service/impl/SysUserServiceImpl.java

对应的增、删方法需要修改

package com.mldong.modules.sys.service.impl;

import java.util.Date;
import java.util.List;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import tk.mybatis.mapper.entity.Condition;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.mldong.base.YesNoEnum;
import com.mldong.common.base.CommonPage;
import com.mldong.modules.sys.dto.SysUserParam;
import com.mldong.modules.sys.entity.SysUser;
import com.mldong.modules.sys.mapper.SysUserMapper;
import com.mldong.modules.sys.service.SysUserService;
/**
 * 用户管理业务
 * @author mldong
 *
 */
@Service
public class SysUserServiceImpl implements SysUserService{
	@Autowired
	private SysUserMapper sysUserMapper;
	@Override
	public int save(SysUserParam param) {
		Date now = new Date();
		SysUser user = new SysUser();
		BeanUtils.copyProperties(param, user);
		user.setCreateTime(now);
		user.setUpdateTime(now);
		user.setIsDeleted(YesNoEnum.NO);
		return sysUserMapper.insertSelective(user);
	}

	@Override
	public int update(SysUserParam param) {
		Date now = new Date();
		SysUser user = new SysUser();
		BeanUtils.copyProperties(param, user);
		user.setUpdateTime(now);
		return sysUserMapper.updateByPrimaryKeySelective(user);
	}

	@Override
	public int remove(List<Long> ids) {
		Date now = new Date();
		SysUser upUser = new SysUser();
		upUser.setIsDeleted(YesNoEnum.YES);
		upUser.setUpdateTime(now);
		Condition condition = new Condition(SysUser.class);
		condition.createCriteria().andIn("id", ids);
		return sysUserMapper.updateByConditionSelective(upUser, condition);
	}

	@Override
	public SysUser get(Long id) {
		return sysUserMapper.selectByPrimaryKey(id);
	}
	@Override
	public CommonPage<SysUser> list(SysUserParam param, int pageNum, int pageSize) {
		Page<SysUser> page = PageHelper.startPage(pageNum, pageSize,true);
		SysUser user = new SysUser();
		BeanUtils.copyProperties(param, user);
		sysUserMapper.select(user);
		return CommonPage.toPage(page);
	}
}
  • mldong-admin/src/main/java/resources/application.yml

配置default-enum-type-handler

# mybatis配置
mybatis:
  type-aliases-package: com.mldong.modules.*.mapper.*,com.mldong.modules.*.dao.*,com.mldong.modules.*.repo.*
  mapper-locations: classpath*:mapper/*/*.xml,classpath*:dao/*/*.xml,classpath*:repo/*/*.xml
  configuration:
    map-underscore-to-camel-case: true
    default-enum-type-handler: com.mldong.base.CodedEnumTypeHandler

启动运行项目

MldongAdminApplication.java

右键->Run As -> Java Application

访问演示

项目源码地址

  • 后端

gitee.com/mldong/mldo…

  • 前端

gitee.com/mldong/mldo…

相关文章

打造一款适合自己的快速开发框架-先导篇

打造一款适合自己的快速开发框架-后端脚手架搭建

打造一款适合自己的快速开发框架-集成mapper

打造一款适合自己的快速开发框架-集成swaggerui和knife4j

打造一款适合自己的快速开发框架-通用类封装之统一结果返回、统一异常处理

打造一款适合自己的快速开发框架-业务错误码规范及实践

打造一款适合自己的快速开发框架-框架分层及CURD样例