//KeyGenerator.java
/**
* 主键生成器接口,有三个实现类:
* 1、 {@link Jdbc3KeyGenerator}
* 2、{@link NoKeyGenerator}
* 3、{@link SelectKeyGenerator}
* @author Clinton Begin
*/
public interface KeyGenerator {
/**
* 针对Sequence主键而言,在执行insert sql前必须指定一个主键值给要插入的记录,
* 如Oracle、DB2,KeyGenerator提供了processBefore()方法。
* @param executor
* @param ms
* @param stmt
* @param parameter
*/
void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
/**
* 针对自增主键的表,在插入时不需要主键,而是在插入过程自动获取一个自增的主键,
* 比如MySQL,Postgresql,KeyGenerator提供了processAfter()方法
* @param executor
* @param ms
* @param stmt
* @param parameter
*/
void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}
如下可能的配置会使用Jdbc3KeyGenerator:
<insert id="insert" parameterType="com.mybatis.cache.mybatiscache.domain.User" useGeneratedKeys="true" keyProperty="id" >
insert into user (id, user_name, pass_word,
real_name, name)
values (#{id,jdbcType=INTEGER}, #{userName,javaType=java.lang.String, jdbcType=VARCHAR}, #{passWord,jdbcType=VARCHAR},
#{realName,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR})
</insert>
useGeneratedKeys="true" keyProperty="id" keyColumn="id"
这里使用了useGeneratedKeys="true"代表我们希望在插入数据库之后获取生成的主键值,因为是在操作数据库之后获取主键,因此processBefore方法是不需要实现的,因此给出的是空实现,源码如下:
org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#processBefore
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// do nothing
}
因此我们只需要看下procesAfter的源码就行了,如下:
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
processBatch(ms, stmt, parameter);
}
public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
final String[] keyProperties = ms.getKeyProperties();
if (keyProperties == null || keyProperties.length == 0) {
return;
}
// 获取JDBC的ResultSet对象,封装有数据库生成的主键信息(因此,mybaits最终还是要依赖于JDBC的机制)
try (ResultSet rs = stmt.getGeneratedKeys()) {
final ResultSetMetaData rsmd = rs.getMetaData();
final Configuration configuration = ms.getConfiguration();
if (rsmd.getColumnCount() < keyProperties.length) {
// Error?
} else {
assignKeys(configuration, rs, rsmd, keyProperties, parameter);
}
} catch (Exception e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
}
}
这里先介绍如何根据jdbc获取到自增主键,看一个demo
mport java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TestJDBC {
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
String sql = "insert into hero values(null,?,?,?)";
try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8","root", "admin");
PreparedStatement ps = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
) {
ps.setString(1, "盖伦");
ps.setFloat(2, 616);
ps.setInt(3, 100);
// 执行插入语句
ps.execute();
// 在执行完插入语句后,MySQL会为新插入的数据分配一个自增长id
// JDBC通过getGeneratedKeys获取该id
ResultSet rs = ps.getGeneratedKeys();
if (rs.next()) {
int id = rs.getInt(1);
System.out.println(id);
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
从jdbc获取到GeneratedKeys之后
进入assignKeys
private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties,
Object parameter) throws SQLException {
if (parameter instanceof ParamMap || parameter instanceof StrictMap) {
// Multi-param or single param with @Param
assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter);
} else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty()
&& ((ArrayList<?>) parameter).get(0) instanceof ParamMap) {
// Multi-param or single param with @Param in batch operation
assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, (ArrayList<ParamMap<?>>) parameter);
} else {
// Single param without @Param
assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);
}
}
对于不同的类型有不同设置的办法,paramMap,集合ParamMap
题外话ParamMap
实际上就是执行查询时,传入多个参数需要指定
int insertParamMap(@Param("user") User record);
然后再对应sql中写上就好了
<insert id="insertParamMap" useGeneratedKeys="true" keyProperty="id">
insert into user (id, user_name, pass_word,
real_name, name)
values (#{user.id,jdbcType=INTEGER}, #{user.userName,javaType=java.lang.String, jdbcType=VARCHAR}, #{user.passWord,jdbcType=VARCHAR},
#{user.realName,jdbcType=VARCHAR}, #{user.name,jdbcType=VARCHAR})
</insert>
org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.KeyAssigner#assign
protected void assign(ResultSet rs, Object param) {
MetaObject metaParam = configuration.newMetaObject(param);
Class<?> propertyType = metaParam.getSetterType(propertyName);
typeHandler = typeHandlerRegistry.getTypeHandler(propertyType,
JdbcType.forCode(rsmd.getColumnType(columnPosition)));
Object value = typeHandler.getResult(rs, columnPosition);
metaParam.setValue(propertyName, value);
}
实际上就是利用jdbc获取主键,,用过属性类型,和jdbc类型获取typeHandler然后利用typeHandler获取匹配类型的数据,设置到对应的parameter中,完成主键的设置,将数据库生成的主键设置回对象
如下配置会执行该方法,在操作数据库之前,生成主键值,最终使用该主键值进行数据库操作:
<insert id="insert" parameterType="com.mybatis.cache.mybatiscache.domain.User">
<selectKey keyProperty="name" keyColumn="name"
resultType="java.lang.String" order="BEFORE">
select uuid()
</selectKey>
insert into user (id, user_name, pass_word,
real_name, name)
values (#{id,jdbcType=INTEGER}, #{userName,javaType=java.lang.String, jdbcType=VARCHAR}, #{passWord,jdbcType=VARCHAR},
#{realName,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR})
</insert>
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
if (executeBefore) {
processGeneratedKeys(executor, ms, parameter);
}
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
if (!executeBefore) {
processGeneratedKeys(executor, ms, parameter);
}
}
org.apache.ibatis.executor.keygen.SelectKeyGenerator#processGeneratedKeys
private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
try {
if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
// 获取配置的属性值,即标签中配置的keyProperty="id"
String[] keyProperties = keyStatement.getKeyProperties();
// 获取全局配置文件对应的Configuration对象
final Configuration configuration = ms.getConfiguration();
// 将传入参数封装为MetaObject对象,方便操作,如设置属性
final MetaObject metaParam = configuration.newMetaObject(parameter);
if (keyProperties != null) {
// 获取执行器,用于执行在 标签中设置的生成主键的sql语句
Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
// keyStatement:是对 标签的封装
// 执行 对应的MappedStatement,生成主键
// 如select UUID()结果为db6fbe60-07af-11ec-978a-1b6197d25533
List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
// 判断长度只能为1,这里使用List接收,是因为底层如此
// 这里只能通过判断元素个数来进行控制
if (values.size() == 0) {
throw new ExecutorException("SelectKey returned no data.");
} else if (values.size() > 1) {
throw new ExecutorException("SelectKey returned more than one value.");
} else {
MetaObject metaResult = configuration.newMetaObject(values.get(0));
// 这里只考虑有一个属性的情况
if (keyProperties.length == 1) {
// 如果是传入的对象的当前属性有getter方法,一般获取的是
// 字符串,所有肯定没有
if (metaResult.hasGetter(keyProperties[0])) {
setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));
} else {
// 设置属性值
// metaParam:用户传入的参数对象
// keyProperties[0]:要设置的属性值
// values.get(0):生成的主键值
// 将生成的主键值设置到传入参数对象的属性中
// 如下是设置前,设置后的值:
// 设置前:{"myage":19,"myname":"379bff59-eb6d-4b03-95d4-571379e9d13f"}
// 设置后:{"id":"db6fbe60-07af-11ec-978a-1b6197d25533","myage":19,"myname":"379bff59-eb6d-4b03-95d4-571379e9d13f"}
setValue(metaParam, keyProperties[0], values.get(0));
}
} else {
handleMultipleProperties(keyProperties, metaParam, metaResult);
}
}
}
}
} catch (ExecutorException e) {
throw e;
} catch (Exception e) {
throw new ExecutorException("Error selecting key or setting result to parameter object. Cause: " + e, e);
}
}
实现也很简单就是新建我们配置的查询
select uuid(),
通过查询数据库获取uuid进行设置
如果生成uuid后需要插入数据库,order="BEFORE"
如果配置成了 order=“AFTER”,那么不会将对应uuid插入到数据库,只会将参数设置uuid返回
不设置主键值,也不获取数据库生成的主键值的情况,为了保持代码的通用性,这里也给出一个实现类处理这种情况,源码如下:
org.apache.ibatis.executor.keygen.NoKeyGenerator
public class NoKeyGenerator implements KeyGenerator {
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
}
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
}
}