Mybatis使用Map<String, Object>作为statement方法的参数

Mybatis使用Map<String, Object>作为statement方法的参数

看到有人吐槽mybatis难用,修改了DB的字段,或者是实体类的字段。都需要去修改xml映射文件。极其麻烦。抛开很多优秀的插件可以解决这个问题不说,其实原生MyBatis其实也是可以解决这个问题。那就是使用Map<String, Object>作为statement方法的参数

使用 Map<String, Object> 参数 Insert

实体类

import java.time.LocalDateTime;

public class FooEntity {
	private Integer id;
	private String name;
	private LocalDateTime createDate;
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public LocalDateTime getCreateDate() {
		return createDate;
	}
	public void setCreateDate(LocalDateTime createDate) {
		this.createDate = createDate;
	}
}

Mapper接口

import java.util.Map;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface FooMapper {
	
	int insert(Map<String, Object> condition);
}

Mapper映射文件

<insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
	INSERT INTO `foo`
	<foreach collection="condition.entrySet()" index="key" item="value" open="(" separator="," close=")">
		<if test="key != null and value != null">`${key}`</if>
	</foreach>
	<foreach collection="condition.entrySet()" index="key" item="value" open=" VALUES(" separator="," close=")">
		<if test="key != null and value != null">#{value}</if>
	</foreach>
</insert>

BeanUtils ,通过内省把 JavaBean 转换为Map的工具类

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class BeanUtils {
	
	/**
	 * JavaBean To Map
	 * @param bean
	 * @param humpToUnderline 驼峰转换为下划线
	 * @return
	 */
	public static Map<String, Object> beanToMap(Object bean, boolean humpToUnderline){
		Objects.requireNonNull(bean);
		Map<String, Object> retVal = new HashMap<>();
		try {
			BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
			PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
			for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
				Method method = propertyDescriptor.getReadMethod();
				if(Modifier.isNative(method.getModifiers())) {
					// 忽略 getClass();
					continue;
				}
				String name = propertyDescriptor.getName();
				
				Object val = method.invoke(bean);
				
				retVal.put(humpToUnderline ? humpToUnderline(name) : name, val);
			}
		} catch (IntrospectionException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			throw new RuntimeException(e);
		}
		return retVal;
	}
	
	// 驼峰转换为下划线
	private static String humpToUnderline(String value) {
		StringBuilder stringBuilder = new StringBuilder();
		char[] chars = value.toCharArray();
		for (char charactor : chars) {
			if (Character.isUpperCase(charactor)) {
				stringBuilder.append("_");
				charactor = Character.toLowerCase(charactor);
			}
			stringBuilder.append(charactor);
		}
		return stringBuilder.toString();
	}
}

Test

@Autowired
private FooMapper fooMapper;

@Test
public void test(){
	FooEntity fooEntity = new FooEntity();
	fooEntity.setName("SpringBoot中文社区");
	fooEntity.setCreateDate(LocalDateTime.now());
	
	Map<String, Object> condition = BeanUtils.beanToMap(fooEntity, true);
	
	int retVal = this.fooMapper.insert(condition);
	
	Integer id = ((BigInteger)condition.get("id")).intValue();
	
	LOGGER.debug("受到影响的行数:{},自增的id:{}", retVal, id);
}
// log
FooMapper.insert  : ==>  Preparing: INSERT INTO `foo` ( `name` , `create_date` ) VALUES( ? , ? ) 
FooMapper.insert  : ==> Parameters: SpringBoot中文社区(String), 2019-11-27T10:49:45.917(LocalDateTime)
FooMapper.insert  : <==    Updates: 1
FooMapperTest  : 受到影响的行数:1,自增的id:1

通过Map<String, Object>作为参数,插入数据。自增id也可以回写到Map,是以BigInteger的形式写入,需要自己转换为 Integer 或者 Long

End

不仅仅是insert,其他的操作使用Map作为参数,配合foreach。就算是不使用第三方插件,也可以很方便的完成需要的功能。并且在修改了DB或者实体类的字段后,不需要修改xml文件。甚至可以单独的吧Map作为参数的crud-mapper抽离出来,其他mapper通过include导入。这也是一种通用的方案。

需要注意的问题就是,foerach遍历的过程中,${key},不是预编译语句。可能导致SQL注入的问题。只要保证Map<String, Object> 不是由前端参数直接构建的,而是通过实体类转换的。那就没问题