InsertOrUpdatePluging
@Intercepts({
@Signature(type = Executor.class,method = "update",args = {MappedStatement.class,Object.class})
})
public class InsertOrUpdatePluging implements Interceptor {
private final ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
private Set<Field> fieldInsertSet;
private Set<Field> fieldUpdateSet;
private SqlCommandType sqlCommandType;
@Override
public Object intercept(Invocation invocation) throws Throwable {
fieldInsertSet = new HashSet<>(0);
fieldUpdateSet = new HashSet<>(0);
MetaObject metaObject = SystemMetaObject.forObject(invocation.getArgs()[0]);
sqlCommandType = (SqlCommandType)metaObject.getValue("sqlCommandType");
Object params = invocation.getArgs()[1];
getAnnotationParams(params);
if(isJavaBean(params)) {
if (sqlCommandType.equals(SqlCommandType.UPDATE)) {
if(fieldUpdateSet.size()>0){
fieldUpdateSet.forEach(f -> {
resolverObject(params, f);
});
}else if(isMapBean(params)){
((Map)params).put("modifyTime",new Date().getTime());
String userName = StringUtils.isEmpty(UserDetailsUtils.getSysRealName())?UserDetailsUtils.getSysUsername()
:UserDetailsUtils.getSysRealName();
((Map)params).put("modifyUser",userName);
}
} else if(sqlCommandType.equals(SqlCommandType.INSERT) && fieldInsertSet.size()>0){
fieldInsertSet.forEach(f -> {
resolverObject(params, f);
});
}
}
invocation.getArgs()[1] = params;
return invocation.proceed();
}
private boolean isMapBean(Object params) {
return Map.class.isAssignableFrom(params.getClass());
}
private void resolverObject(Object o,Field f){
MetaObject argObject = SystemMetaObject.forObject(o);
Object value = null;
if(f.getDeclaredAnnotation(CreateTime.class) != null || f.getDeclaredAnnotation(ModifyTime.class) != null){
if(f.getDeclaredAnnotation(CreateTime.class) != null){
CreateTime createTime = f.getDeclaredAnnotation(CreateTime.class);
value = resovelTime(createTime.type(),createTime.partten());
}else{
ModifyTime modifyTime = f.getDeclaredAnnotation(ModifyTime.class);
value = resovelTime(modifyTime.type(),modifyTime.partten());
}
}else{
value = StringUtils.isEmpty(UserDetailsUtils.getSysRealName())?UserDetailsUtils.getSysUsername()
:UserDetailsUtils.getSysRealName();
}
argObject.setValue(f.getName(),value);
}
private Object resovelTime(Timesolt clazz,String partten){
Object value;
switch (clazz){
case DATE:value = new Date();break;
case STRING: {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(partten);
value = simpleDateFormat.format(new Date());
break;
}
case LONG:
default:value = new Date().getTime();
}
return value;
}
private boolean isJavaBean(Object o){
Reflector reflector = reflectorFactory.findForClass(o.getClass());
return reflector.getGetablePropertyNames().length > 0 || reflector.getSetablePropertyNames().length > 0;
}
private void getAnnotationParams(Object o) {
Field[] declaredFields = o.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
if(declaredField.getAnnotation(CreateTime.class) != null
|| declaredField.getAnnotation(CreateUser.class) != null){
fieldInsertSet.add(declaredField);
}
if(declaredField.getAnnotation(ModifyTime.class) != null
|| declaredField.getAnnotation(ModifyUser.class) != null){
fieldInsertSet.add(declaredField);
fieldUpdateSet.add(declaredField);
}
}
}
@Override
public Object plugin(Object o) {
return Plugin.wrap(o,this);
}
@Override
public void setProperties(Properties properties) {
}
}
这个插件的作用是在插入数据或者更新数据时候只需要在实体类加上@CreateTime 、@ModifyTime 、@CreateUser、@ModifyUser注解即可在数据插入或者更新到数据库时候自动填充创建时间,修改时间,创建人,修改人等数据,会根据给定注解选择填充值。UserDetailsUtils为封装的Spring security工具类,可以获取当前登录用户信息。这个插件拓展的是Executor执行器的update方法,因为mybatis插入更新其实都是调用的mybatis的Executor中的update方法,invocation.getArgs()方法得到的其实就是@Signature注解里面的args参数,也就是MappedStatement.class以及Object.class,MappedStatement其实就是对应着接口中的一个个方法的,其中包含了sqlCommandType,MetaObject 也是mybatis的一个工具类,其中通过反射将对象的信息全部保存为自己的属性,并对外外提供一系列方法,比如设置值,取值等等。我们可以通过MetaObject 拿到MappedStatement里的sqlCommandType类型来判断当前操作是新增还是更新。第二个Object类型的参数就是我们更新或者新增是传递的对象,只需要根据注解以及sqlCommandType类型以及上面所述的四个注解像第二个参数对象填充即可。
至于如何将自定义插件注册,SqlSessionFactoryBean中有Mybatis中的Configuration对象,我们可以通过SqlSessionFactoryBean像mybatis注册插件,下面是我在项目中注册插件代码。
MybatisHrMysqlConfig
@Configuration
@MapperScan(basePackages = {"com.gkss.hr.web.dao.hr.**"},
sqlSessionTemplateRef = "hrSqlSessionTemplateMysql")
public class MybatisHrMysqlConfig implements ResourceLoaderAware {
private final static String DAO_CLASS_PATH = "classpath*:com/gkss/hr/web/dao/hr/*.class";
@Autowired
@Qualifier("hrDataSource")
private DataSource mysql;
private ResourceLoader resourceLoader;
@Bean(name = "hrMysqlTransactionManager") // 配置事务
public DataSourceTransactionManager testTransactionManager() {
return new DataSourceTransactionManager(mysql);
}
@Bean(name = "hrSqlSessionTemplateMysql")
public SqlSessionTemplate sqlSessionTemplateMysql() throws Exception {
SqlSessionTemplate template = new SqlSessionTemplate(this.sqlSessionFactoryMysql());
return template;
}
@Primary
@Bean("hrSqlSessionFactoryMysql")
public SqlSessionFactory sqlSessionFactoryMysql() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setTypeHandlers(new TypeHandler[]{new StringTypeHandler(),new IntegerTypeHandler(),new JSONArrayHandler()});
//插件
factoryBean.setPlugins(new Interceptor[]{new InsertOrUpdatePluging()});
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
//数据源
factoryBean.setDataSource(mysql);
// 设置mybatis的xml所在位置
factoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/hr/*.xml"));
//开启驼峰
configuration.setMapUnderscoreToCamelCase(true);
putAllcolumnAndConditionForConfiguretion(configuration,true);
factoryBean.setConfiguration(configuration);
return factoryBean.getObject();
}
private void putAllcolumnAndConditionForConfiguretion(org.apache.ibatis.session.Configuration configuration,boolean resolve) throws Exception {
List<Class<?>> classByPath = getClassByPath(DAO_CLASS_PATH);
classByPath.forEach(v->{
SqlEntity declaredAnnotation = v.getDeclaredAnnotation(SqlEntity.class);
if(declaredAnnotation != null){
StringBuffer allcolumnStr = new StringBuffer("");
StringBuffer conditionStr = new StringBuffer("");
Class<?> clazz = declaredAnnotation.clazz();
String prifex = declaredAnnotation.prifex().isEmpty()?"":declaredAnnotation.prifex()+".";
for(Field field : clazz.getDeclaredFields()){
field.setAccessible(true);
if(field.getDeclaredAnnotation(Transient.class) == null){
allcolumnStr.append(prifex+(resolve?underscoreName(field.getName(),resolve):field.getName())+",");
String fieldN = resolve ? underscoreName(field.getName(),resolve) : field.getName();
if(field.getType().isAssignableFrom(String.class)){
conditionStr.append(" <if test=\""+field.getName()+" != null and "+field.getName()+" != ''\">");
}else{
conditionStr.append(" <if test=\""+field.getName()+" != null\">");
}
conditionStr.append("and "+prifex+fieldN+"=#{"+field.getName()+"}");
conditionStr.append(" </if>");
}
}
final String MAPPER_AllCOLUMN = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
+ "<sql>\n"
+ " "+allcolumnStr.substring(0,allcolumnStr.length()-1)+"\n"
+ "</sql>\n";
final String MAPPER_CONDITION = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
+ "<sql>\n"
+ " "+conditionStr.toString()+"\n"
+ "</sql>\n";
configuration.getSqlFragments().put(v.getName()+".condition",new XPathParser(MAPPER_CONDITION).evalNode("sql"));
configuration.getSqlFragments().put(v.getName()+".allcolumn",new XPathParser(MAPPER_AllCOLUMN).evalNode("sql"));
}
});
}
private List<Class<?>> getClassByPath(String path) throws Exception{
List<Class<?>> classList = new ArrayList<Class<?>>();
ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
MetadataReaderFactory metaReader = new CachingMetadataReaderFactory(resourceLoader);
Resource[] resources = resolver.getResources(path);
for (Resource r : resources) {
MetadataReader reader = metaReader.getMetadataReader(r);
Class<?> clazz = Class.forName(reader.getClassMetadata().getClassName());
classList.add(clazz);
}
return classList;
}
/**
* 将驼峰式命名的字符串转换为下划线大写方式。如果转换前的驼峰式命名的字符串为空,则返回空字符串。</br>
* 例如:HelloWorld->hello_word
* @param param 转换前的驼峰式命名的字符串
* @return 转换后下划线大写方式命名的字符串
*/
public static String underscoreName(String param,boolean lowcase) {
if (param == null || "".equals(param.trim())) {
return "";
}
int len = param.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
char c = param.charAt(i);
if (Character.isUpperCase(c)) {
sb.append("_");
}
if (lowcase) {
sb.append(Character.toLowerCase(c)); //统一都转大写
} else {
sb.append(Character.toUpperCase(c)); //统一都转小写
}
}
return sb.toString();
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public static void main(String[] args) {
System.out.println(underscoreName("userId",true));
}
}
其中有个putAllcolumnAndConditionForConfiguretion方法用来扫描指定包下面的所有Dao接口,拿到其中包含@SqlEntity注解Dao通过反射将属性拼接成Sql然后注册到Configuretion的sqlFragments属性中,这个属性是个Map类型对象,里面保存了<sql>标签定义的sql语句片段,之前XML中每次都要写如下图这种<sql>片段
上面的putAllcolumnAndConditionForConfiguretion方法会在项目启动时自动将这些SQL片段注册到Configuretion中,以后编写XML时省去了写上图SQL片段的步骤,简便开发提高开发速度。使用如下图
在Dao接口上面标注@SqlEntity注解,直接在XML使用include标签即可。
注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SqlEntity {
Class<?> clazz();
String prifex() default "";
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CreateTime {
Timesolt type() default Timesolt.LONG;
String partten() default "yyyy-MM-dd hh:mm:ss";
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CreateUser {
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ModifyTime {
Timesolt type() default Timesolt.LONG;
String partten() default "yyyy-MM-dd hh:mm:ss";
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ModifyUser {
}
UserDetailsUtils
public class UserDetailsUtils {
/**
* 获取当前登录用户权限.
*/
public static List<HrPermission> getSysUserPermissionList(Integer permissionType) {
if ("anonymousUser".equals(SecurityContextHolder.getContext().getAuthentication().getPrincipal())) {
return new ArrayList<>(0);
}
return ((UsersDetail) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getPermissionMap().get(permissionType);
}
/**
* 获取当前登录用户权限.
*/
public static List<HrDepartment> getSysUserDepartmentList() {
if ("anonymousUser".equals(SecurityContextHolder.getContext().getAuthentication().getPrincipal())) {
return new ArrayList<>(0);
}
return ((UsersDetail) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getHasDepartmentList();
}
/**
* 获取当前登录用户权限.
*/
public static List<HrDepartment> getSysUserDepartmentTreeList() {
if ("anonymousUser".equals(SecurityContextHolder.getContext().getAuthentication().getPrincipal())) {
return new ArrayList<>(0);
}
return ((UsersDetail) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getHasDepartmentTreeList();
}
/**
* 获取当前登录用户账号.
*/
public static String getSysUsername() {
if ("anonymousUser".equals(SecurityContextHolder.getContext().getAuthentication().getPrincipal())) {
return "";
}
return ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername();
}
public static String getSysRealName() {
if ("anonymousUser".equals(SecurityContextHolder.getContext().getAuthentication().getPrincipal())) {
return "";
}
return ((UsersDetail) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getHrUser().getRealname();
}
/**
* 获取当前登录用户实体.
*/
public static HrUser getSysUser() {
if ("anonymousUser".equals(SecurityContextHolder.getContext().getAuthentication().getPrincipal())) {
return null;
}
return ((UsersDetail) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getHrUser();
}
/**
* 获取当前登录用户实体.
*/
public static Integer getSysUserId() {
if ("anonymousUser".equals(SecurityContextHolder.getContext().getAuthentication().getPrincipal())) {
return null;
}
return ((UsersDetail) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getHrUser().getUserId();
}
/**
* 获取当前登录用户实体.
*/
public static UsersDetail getSysUserDetail() {
if ("anonymousUser".equals(SecurityContextHolder.getContext().getAuthentication().getPrincipal())) {
return null;
}
return ((UsersDetail) SecurityContextHolder.getContext().getAuthentication().getPrincipal());
}
/**
* 全局获取HttpServletResponse对象
*/
public static HttpServletResponse getHttpServletResponse() {
HttpServletResponse response = ((ServletWebRequest) RequestContextHolder.getRequestAttributes()).getResponse();
return response;
}
/**
* 全局获取HttpServletRequest对象
*/
public static HttpServletRequest getHttpServletRequest() {
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
return request;
}
}
上面是mybatis注册插件的代码片段,是通过责任链以及动态代理来实现插件拓展的。在参数准备,结果处理,语句准备和语句执行时均可以注册自己编写的插件来改变mybatis的行为。