淺談MyBatis Plus主鍵設(shè)置策略
根據(jù)一次插入失敗報(bào)錯(cuò)來了解下MyBatis Plus主鍵設(shè)置策略
今天學(xué)習(xí)使用MyBatis Plus,發(fā)現(xiàn)使用代碼生成器生成對(duì)應(yīng)的實(shí)體類、Service和Mapper后,在保存數(shù)據(jù)時(shí)報(bào)錯(cuò)
com.baomidou.mybatisplus.exceptions.MybatisPlusException: java.lang.reflect.InvocationTargetException
at com.baomidou.mybatisplus.MybatisSqlSessionTemplate$SqlSessionInterceptor.invoke(MybatisSqlSessionTemplate.java:405)
at com.sun.proxy.$Proxy70.insert(Unknown Source)
at com.baomidou.mybatisplus.MybatisSqlSessionTemplate.insert(MybatisSqlSessionTemplate.java:243)
at com.baomidou.mybatisplus.activerecord.Model.insert(Model.java:56)
at com.lemon.rabbit.common.base.city.CityInitial.printInfo(CityInitial.java:112)
at com.lemon.rabbit.common.base.city.CityInitial.parseNextLevel(CityInitial.java:87)
at com.lemon.rabbit.common.base.city.CityInitial.test(CityInitial.java:59)
at com.lemon.rabbit.RabbitApplicationTests.contextLoads(RabbitApplicationTests.java:19)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.baomidou.mybatisplus.MybatisSqlSessionTemplate$SqlSessionInterceptor.invoke(MybatisSqlSessionTemplate.java:401)
... 37 more
Caused by: org.apache.ibatis.exceptions.PersistenceException:
### Error updating database. Cause: org.apache.ibatis.reflection.ReflectionException: Could not set property 'id' of 'class com.lemon.rabbit.model.City' with value '1017367047558582273' Cause: java.lang.IllegalArgumentException: argument type mismatch
### Cause: org.apache.ibatis.reflection.ReflectionException: Could not set property 'id' of 'class com.lemon.rabbit.model.City' with value '1017367047558582273' Cause: java.lang.IllegalArgumentException: argument type mismatch
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:200)
at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:185)
... 42 more
Caused by: org.apache.ibatis.reflection.ReflectionException: Could not set property 'id' of 'class com.lemon.rabbit.model.City' with value '1017367047558582273' Cause: java.lang.IllegalArgumentException: argument type mismatch
at org.apache.ibatis.reflection.wrapper.BeanWrapper.setBeanProperty(BeanWrapper.java:185)
at org.apache.ibatis.reflection.wrapper.BeanWrapper.set(BeanWrapper.java:59)
at org.apache.ibatis.reflection.MetaObject.setValue(MetaObject.java:140)
at com.baomidou.mybatisplus.MybatisDefaultParameterHandler.populateKeys(MybatisDefaultParameterHandler.java:217)
at com.baomidou.mybatisplus.MybatisDefaultParameterHandler.processBatch(MybatisDefaultParameterHandler.java:156)
at com.baomidou.mybatisplus.MybatisDefaultParameterHandler.<init>(MybatisDefaultParameterHandler.java:71)
at com.baomidou.mybatisplus.MybatisXMLLanguageDriver.createParameterHandler(MybatisXMLLanguageDriver.java:37)
at org.apache.ibatis.session.Configuration.newParameterHandler(Configuration.java:545)
at org.apache.ibatis.executor.statement.BaseStatementHandler.<init>(BaseStatementHandler.java:69)
at org.apache.ibatis.executor.statement.PreparedStatementHandler.<init>(PreparedStatementHandler.java:40)
at org.apache.ibatis.executor.statement.RoutingStatementHandler.<init>(RoutingStatementHandler.java:46)
at org.apache.ibatis.session.Configuration.newStatementHandler(Configuration.java:558)
at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:48)
at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117)
at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76)
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:198)
... 43 more
Caused by: java.lang.IllegalArgumentException: argument type mismatch
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.ibatis.reflection.invoker.MethodInvoker.invoke(MethodInvoker.java:41)
at org.apache.ibatis.reflection.wrapper.BeanWrapper.setBeanProperty(BeanWrapper.java:180)
... 58 more實(shí)體類City的主鍵是Integer類型的,在進(jìn)行insert操作時(shí),MyBatis Plus自動(dòng)生成了一個(gè)Long類型的主鍵id,導(dǎo)致參數(shù)類型不匹配,出現(xiàn)上述錯(cuò)誤
經(jīng)過查看日志和調(diào)試發(fā)現(xiàn),MyBatis最終調(diào)用BeanWrapper的setBeanProperty方法,通過反射執(zhí)行最終的插入操作(增刪改查應(yīng)該都是通過此處的反射,不過暫時(shí)只調(diào)試了insert方法)
private void setBeanProperty(PropertyTokenizer prop, Object object, Object value) {
try {
Invoker method = metaClass.getSetInvoker(prop.getName());
Object[] params = {value};
try {
method.invoke(object, params);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
} catch (Throwable t) {
throw new ReflectionException("Could not set property '" + prop.getName() + "' of '" + object.getClass() + "' with value '" + value + "' Cause: " + t.toString(), t);
}
}
此處傳入的object即為我們想要保存到數(shù)據(jù)庫的實(shí)體信息(不帶ID信息),value為主鍵信息,此時(shí)主鍵值已經(jīng)是一個(gè)Long類型的值,我們接著向上看value是哪里傳過來的
setBeanProperty是一個(gè)私有方法,在本類調(diào)用,查詢看到set(PropertyTokenizer prop, Object value)方法調(diào)用了它
@Override
public void set(PropertyTokenizer prop, Object value) {
if (prop.getIndex() != null) {
Object collection = resolveCollection(prop, object);
setCollectionValue(prop, collection, value);
} else {
setBeanProperty(prop, object, value);
}
}set方法中的value也是其他地方傳入的
Caused by: org.apache.ibatis.reflection.ReflectionException: Could not set property 'id' of 'class com.lemon.rabbit.model.City' with value '1017367047558582273' Cause: java.lang.IllegalArgumentException: argument type mismatch
at org.apache.ibatis.reflection.wrapper.BeanWrapper.setBeanProperty(BeanWrapper.java:185)
at org.apache.ibatis.reflection.wrapper.BeanWrapper.set(BeanWrapper.java:59)
at org.apache.ibatis.reflection.MetaObject.setValue(MetaObject.java:140)
at com.baomidou.mybatisplus.MybatisDefaultParameterHandler.populateKeys(MybatisDefaultParameterHandler.java:217)
at com.baomidou.mybatisplus.MybatisDefaultParameterHandler.processBatch(MybatisDefaultParameterHandler.java:156)
at com.baomidou.mybatisplus.MybatisDefaultParameterHandler.<init>(MybatisDefaultParameterHandler.java:71)
at com.baomidou.mybatisplus.MybatisXMLLanguageDriver.createParameterHandler(MybatisXMLLanguageDriver.java:37)
at org.apache.ibatis.session.Configuration.newParameterHandler(Configuration.java:545)
at org.apache.ibatis.executor.statement.BaseStatementHandler.<init>(BaseStatementHandler.java:69)
at org.apache.ibatis.executor.statement.PreparedStatementHandler.<init>(PreparedStatementHandler.java:40)
at org.apache.ibatis.executor.statement.RoutingStatementHandler.<init>(RoutingStatementHandler.java:46)
at org.apache.ibatis.session.Configuration.newStatementHandler(Configuration.java:558)
at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:48)
at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117)
at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76)
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:198)
... 43 more
查看報(bào)錯(cuò)日志可以看到,BeanWrapper的上一層是MetaObject,我們找到MetaObject,看到在getValue方法中調(diào)用了BeanWrapper的set方法(BeanWrapper實(shí)現(xiàn)了ObjectWrapper)
public void setValue(String name, Object value) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
if (value == null && prop.getChildren() != null) {
// don't instantiate child path if value is null
return;
} else {
metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
}
}
metaValue.setValue(prop.getChildren(), value);
} else {
objectWrapper.set(prop, value);
}
}MetaObject中的主鍵值也是上層調(diào)用傳入的,繼續(xù)根據(jù)錯(cuò)誤日志向上看:MybatisDefaultParameterHandler
/**
* <p>
* 自定義元對(duì)象填充控制器
* </p>
*
* @param metaObjectHandler 元數(shù)據(jù)填充處理器
* @param tableInfo 數(shù)據(jù)庫表反射信息
* @param ms MappedStatement
* @param parameterObject 插入數(shù)據(jù)庫對(duì)象
* @return Object
*/
protected static Object populateKeys(MetaObjectHandler metaObjectHandler, TableInfo tableInfo,
MappedStatement ms, Object parameterObject) {
if (null == tableInfo || StringUtils.isEmpty(tableInfo.getKeyProperty()) || null == tableInfo.getIdType()) {
/* 不處理 */
return parameterObject;
}
/* 自定義元對(duì)象填充控制器 */
MetaObject metaObject = ms.getConfiguration().newMetaObject(parameterObject);
if (ms.getSqlCommandType() == SqlCommandType.INSERT) {
if (tableInfo.getIdType().getKey() >= 2) {
Object idValue = metaObject.getValue(tableInfo.getKeyProperty());
/* 自定義 ID */
if (StringUtils.checkValNull(idValue)) {
if (tableInfo.getIdType() == IdType.ID_WORKER) {
metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.getId());
} else if (tableInfo.getIdType() == IdType.ID_WORKER_STR) {
metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.getIdStr());
} else if (tableInfo.getIdType() == IdType.UUID) {
metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.get32UUID());
}
}
}
// 插入填充
if (metaObjectHandler.openInsertFill()) {
metaObjectHandler.insertFill(metaObject);
}
} else if (ms.getSqlCommandType() == SqlCommandType.UPDATE && metaObjectHandler.openUpdateFill()) {
// 更新填充
metaObjectHandler.updateFill(metaObject);
}
return metaObject.getOriginalObject();
}在populateKeys方法中調(diào)用了metaObject.setValue()
if (StringUtils.checkValNull(idValue)) {
if (tableInfo.getIdType() == IdType.ID_WORKER) {
metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.getId());
} else if (tableInfo.getIdType() == IdType.ID_WORKER_STR) {
metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.getIdStr());
} else if (tableInfo.getIdType() == IdType.UUID) {
metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.get32UUID());
}
}可以看到,此處根據(jù)IdType生成不同類型的主鍵id,IdType是一個(gè)枚舉類,定義了生成ID的類型
- AUTO 數(shù)據(jù)庫ID自增
- INPUT 用戶輸入ID
- ID_WORKER 全局唯一ID,Long類型的主鍵
- ID_WORKER_STR 字符串全局唯一ID
- UUID 全局唯一ID,UUID類型的主鍵
- NONE 該類型為未設(shè)置主鍵類型
當(dāng)IdType的類型為ID_WORKER、ID_WORKER_STR或者UUID時(shí),主鍵由MyBatis Plus的IdWorker類生成
ID_WORKER
調(diào)用IdWorker的getId()方法,生成一個(gè)與時(shí)間相關(guān)的主鍵id
public static long getId() {
return worker.nextId();
}IdWorker的getId()方法引用了Sequence的nextId()方法
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {//閏秒
long offset = lastTimestamp - timestamp;
if (offset <= 5) {
try {
wait(offset << 1);
timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
}
}
if (lastTimestamp == timestamp) {
// 相同毫秒內(nèi),序列號(hào)自增
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 同一毫秒的序列數(shù)已經(jīng)達(dá)到最大
timestamp = tilNextMillis(lastTimestamp);
}
} else {
// 不同毫秒內(nèi),序列號(hào)置為 1 - 3 隨機(jī)數(shù)
sequence = ThreadLocalRandom.current().nextLong(1, 3);
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift) // 時(shí)間戳部分
| (datacenterId << datacenterIdShift) // 數(shù)據(jù)中心部分
| (workerId << workerIdShift) // 機(jī)器標(biāo)識(shí)部分
| sequence; // 序列號(hào)部分
}
ID_WORKER_STR
將worker.nextId()的返回值轉(zhuǎn)化為字符串,和getId()方法相似
public static String getIdStr() {
return String.valueOf(worker.nextId());
}UUID
去除中劃線的UUID字符串
public static synchronized String get32UUID() {
return UUID.randomUUID().toString().replace("-", "");
}此時(shí),已經(jīng)基本可以確認(rèn),問題出在IdType的配置上,那么這個(gè)IdType從哪里獲取的呢?TableInfo中獲取的!
TableInfo是數(shù)據(jù)庫表反射信息實(shí)體類,此處由其他方法傳入的,查看日志:
at com.baomidou.mybatisplus.MybatisDefaultParameterHandler.populateKeys(MybatisDefaultParameterHandler.java:217) at com.baomidou.mybatisplus.MybatisDefaultParameterHandler.processBatch(MybatisDefaultParameterHandler.java:156)
通過日志可以看出,populateKeys()方法是在processBatch()方法中調(diào)用的,找到該方法
/**
* <p>
* 批量(填充主鍵 ID)
* </p>
*
* @param ms
* @param parameterObject 插入數(shù)據(jù)庫對(duì)象
* @return
*/
protected static Object processBatch(MappedStatement ms, Object parameterObject) {
//檢查parameterObject
if (null == parameterObject) {
return null;
}
boolean isFill = false;
// 全局配置是否配置填充器
MetaObjectHandler metaObjectHandler = GlobalConfigUtils.getMetaObjectHandler(ms.getConfiguration());
/* 只處理插入或更新操作 */
if (ms.getSqlCommandType() == SqlCommandType.INSERT) {
isFill = true;
} else if (ms.getSqlCommandType() == SqlCommandType.UPDATE
&& metaObjectHandler.openUpdateFill()) {
isFill = true;
}
if (isFill) {
Collection<Object> parameters = getParameters(parameterObject);
if (null != parameters) {
List<Object> objList = new ArrayList<>();
for (Object parameter : parameters) {
TableInfo tableInfo = TableInfoHelper.getTableInfo(parameter.getClass());
if (null != tableInfo) {
objList.add(populateKeys(metaObjectHandler, tableInfo, ms, parameter));
} else {
/*
* 非表映射類不處理
*/
objList.add(parameter);
}
}
return objList;
} else {
TableInfo tableInfo = null;
if (parameterObject instanceof Map) {
Map map = (Map) parameterObject;
if (map.containsKey("et")) {
Object et = map.get("et");
if (et != null) {
if (et instanceof Map) {
Map realEtMap = (Map) et;
if (realEtMap.containsKey("MP_OPTLOCK_ET_ORIGINAL")) {//refer to OptimisticLockerInterceptor.MP_OPTLOCK_ET_ORIGINAL
tableInfo = TableInfoHelper.getTableInfo(realEtMap.get("MP_OPTLOCK_ET_ORIGINAL").getClass());
}
} else {
tableInfo = TableInfoHelper.getTableInfo(et.getClass());
}
}
}
} else {
tableInfo = TableInfoHelper.getTableInfo(parameterObject.getClass());
}
return populateKeys(metaObjectHandler, tableInfo, ms, parameterObject);
}
}
return parameterObject;
}
下面對(duì)TableInfo生成的這段代碼做個(gè)說明
TableInfo tableInfo = null;
if (parameterObject instanceof Map) {
Map map = (Map) parameterObject;
if (map.containsKey("et")) {
Object et = map.get("et");
if (et != null) {
if (et instanceof Map) {
Map realEtMap = (Map) et;
if (realEtMap.containsKey("MP_OPTLOCK_ET_ORIGINAL")) {//refer to OptimisticLockerInterceptor.MP_OPTLOCK_ET_ORIGINAL
tableInfo = TableInfoHelper.getTableInfo(realEtMap.get("MP_OPTLOCK_ET_ORIGINAL").getClass());
}
} else {
tableInfo = TableInfoHelper.getTableInfo(et.getClass());
}
}
}
} else {
tableInfo = TableInfoHelper.getTableInfo(parameterObject.getClass());
}parameterObject:要保存到數(shù)據(jù)庫中的實(shí)體類信息
我在保存數(shù)據(jù)時(shí),參數(shù)形式并不是Map類型的,所以直接跳轉(zhuǎn)到else中
tableInfo = TableInfoHelper.getTableInfo(parameterObject.getClass());
根據(jù)保存的實(shí)體類的類型去獲取數(shù)據(jù)庫表反射信息
我們看下getTableInfo()方法的實(shí)現(xiàn)
public static TableInfo getTableInfo(Class<?> clazz) {
return tableInfoCache.get(ClassUtils.getUserClass(clazz).getName());
}從tableInfoCache中獲取指定類型的數(shù)據(jù)庫反射信息。tableInfoCache是一個(gè)線程安全的私有靜態(tài)Map,主要用于存放類型和數(shù)據(jù)庫表的映射關(guān)系。斷點(diǎn)調(diào)試到此處,看下tableInfoCache的內(nèi)容

可以看到,idType的值為ID_WORKER,即生成一個(gè)與時(shí)間相關(guān)的Long類型的id。在放入到tableInfoCache的時(shí)候就已經(jīng)指定了idType的值。
查看TableInfoHelper的源碼可以得知,initTableInfo()方法負(fù)責(zé)initTableInfo的初始化
public synchronized static TableInfo initTableInfo(MapperBuilderAssistant builderAssistant, Class<?> clazz) {
//檢測(cè)是否已存在
TableInfo tableInfo = tableInfoCache.get(clazz.getName());
if (StringUtils.checkValNotNull(tableInfo)) {
if (StringUtils.checkValNotNull(builderAssistant)) {
tableInfo.setConfigMark(builderAssistant.getConfiguration());
}
return tableInfo;
}
tableInfo = new TableInfo();
GlobalConfiguration globalConfig;
if (null != builderAssistant) {
tableInfo.setCurrentNamespace(builderAssistant.getCurrentNamespace());
tableInfo.setConfigMark(builderAssistant.getConfiguration());
//獲取全局配置,其中包括了idType
globalConfig = GlobalConfigUtils.getGlobalConfig(builderAssistant.getConfiguration());
} else {
// 兼容測(cè)試場(chǎng)景
globalConfig = GlobalConfigUtils.DEFAULT;
}
/* 表名 */
TableName table = clazz.getAnnotation(TableName.class);
String tableName = clazz.getSimpleName();
if (table != null && StringUtils.isNotEmpty(table.value())) {
tableName = table.value();
} else {
// 開啟字段下劃線申明
if (globalConfig.isDbColumnUnderline()) {
tableName = StringUtils.camelToUnderline(tableName);
}
// 大寫命名判斷
if (globalConfig.isCapitalMode()) {
tableName = tableName.toUpperCase();
} else {
// 首字母小寫
tableName = StringUtils.firstToLowerCase(tableName);
}
// 存在表前綴
if (null != globalConfig.getTablePrefix()) {
tableName = globalConfig.getTablePrefix() + tableName;
}
}
tableInfo.setTableName(tableName);
// 開啟了自定義 KEY 生成器
if (null != globalConfig.getKeyGenerator()) {
tableInfo.setKeySequence(clazz.getAnnotation(KeySequence.class));
}
/* 表結(jié)果集映射 */
if (table != null && StringUtils.isNotEmpty(table.resultMap())) {
tableInfo.setResultMap(table.resultMap());
}
List<TableFieldInfo> fieldList = new ArrayList<>();
List<Field> list = getAllFields(clazz);
// 標(biāo)記是否讀取到主鍵
boolean isReadPK = false;
boolean existTableId = existTableId(list);
for (Field field : list) {
/*
* 主鍵ID 初始化
*/
if (!isReadPK) {
if (existTableId) {
isReadPK = initTableId(globalConfig, tableInfo, field, clazz);
} else {
isReadPK = initFieldId(globalConfig, tableInfo, field, clazz);
}
if (isReadPK) {
continue;
}
}
/*
* 字段初始化
*/
if (initTableField(globalConfig, tableInfo, fieldList, field, clazz)) {
continue;
}
/*
* 字段, 使用 camelToUnderline 轉(zhuǎn)換駝峰寫法為下劃線分割法, 如果已指定 TableField , 便不會(huì)執(zhí)行這里
*/
fieldList.add(new TableFieldInfo(globalConfig, tableInfo, field));
}
/* 字段列表 */
tableInfo.setFieldList(globalConfig, fieldList);
/*
* 未發(fā)現(xiàn)主鍵注解,提示警告信息
*/
if (StringUtils.isEmpty(tableInfo.getKeyColumn())) {
logger.warn(String.format("Warn: Could not find @TableId in Class: %s.", clazz.getName()));
}
/*
* 注入
*/
tableInfoCache.put(clazz.getName(), tableInfo);
return tableInfo;
}在existTableId方法中判斷主鍵注解@TableId是否存在
/**
* <p>
* 判斷主鍵注解是否存在
* </p>
*
* @param list 字段列表
* @return
*/
public static boolean existTableId(List<Field> list) {
boolean exist = false;
for (Field field : list) {
TableId tableId = field.getAnnotation(TableId.class);
if (tableId != null) {
exist = true;
break;
}
}
return exist;
}當(dāng)不存在主鍵注解時(shí),會(huì)調(diào)用initFieldId()方法對(duì)主鍵屬性進(jìn)行初始化
private static boolean initFieldId(GlobalConfiguration globalConfig, TableInfo tableInfo, Field field, Class<?> clazz) {
String column = field.getName();
if (globalConfig.isCapitalMode()) {
column = column.toUpperCase();
}
if (DEFAULT_ID_NAME.equalsIgnoreCase(column)) {
if (StringUtils.isEmpty(tableInfo.getKeyColumn())) {
tableInfo.setIdType(globalConfig.getIdType());
tableInfo.setKeyColumn(column);
tableInfo.setKeyProperty(field.getName());
return true;
} else {
throwExceptionId(clazz);
}
}
return false;
}至此, 我們基本可以判斷是因?yàn)樵趯?shí)體類City中id屬性沒有加@TableId注解,我們看下TableId的源碼
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TableId {
/**
* <p>
* 字段值(駝峰命名方式,該值可無)
* </p>
*/
String value() default "";
/**
* <p>
* 主鍵ID
* </p>
* {@link IdType}
*/
IdType type() default IdType.NONE;
}TableId的類型通過type來指定,默認(rèn)是IdType.NONE(該類型為未設(shè)置主鍵類型),City表是通過數(shù)據(jù)庫的自增序列實(shí)現(xiàn)的,所以設(shè)置為AUTO
@TableId(type = IdType.AUTO) private Integer id;
然后測(cè)試,程序正常運(yùn)行,保存數(shù)據(jù)成功。
擴(kuò)展
對(duì)于tableInfo默認(rèn)的idType值配置,可以看出用的是全局配置的idType,全局配置的值是在initTableInfo()方法中獲取的,有興趣的話可以去看看全局配置的實(shí)現(xiàn),此處暫不深入了
GlobalConfiguration globalConfig;
if (null != builderAssistant) {
tableInfo.setCurrentNamespace(builderAssistant.getCurrentNamespace());
tableInfo.setConfigMark(builderAssistant.getConfiguration());
globalConfig = GlobalConfigUtils.getGlobalConfig(builderAssistant.getConfiguration());
} else {
// 兼容測(cè)試場(chǎng)景
globalConfig = GlobalConfigUtils.DEFAULT;
}到此這篇關(guān)于淺談MyBatis Plus主鍵設(shè)置策略的文章就介紹到這了,更多相關(guān)MyBatis Plus主鍵設(shè)置內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringValidation自定義注解及分組校驗(yàn)功能詳解
這篇文章主要介紹了SpringValidation自定義注解及分組校驗(yàn)功能,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01
解決異常FileNotFoundException:class path resource找不到資源文件的問題
今天小編就為大家分享一篇關(guān)于解決異常FileNotFoundException:class path resource找不到資源文件的問題,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12
RSA加密算法java簡(jiǎn)單實(shí)現(xiàn)方法(必看)
下面小編就為大家?guī)硪黄猂SA加密算法java簡(jiǎn)單實(shí)現(xiàn)方法(必看)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-09-09
Java Shutdown Hook場(chǎng)景使用及源碼分析
shutdown hook 就是一個(gè)簡(jiǎn)單的已初始化但是未啟動(dòng)的線程,本文詳細(xì)的介紹了Java Shutdown Hook場(chǎng)景使用及源碼分析,感興趣的朋友可以參考一下2021-06-06
Mybatis批量插入index out of range錯(cuò)誤的解決(較偏的錯(cuò)誤)
這篇文章主要介紹了Mybatis批量插入index out of range錯(cuò)誤的解決(較偏的錯(cuò)誤),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
Java數(shù)據(jù)結(jié)構(gòu)之樹和二叉樹的相關(guān)資料
這篇文章主要介紹了Java?數(shù)據(jù)結(jié)構(gòu)之樹和二叉樹相關(guān)資料,文中通過示例代碼和一些相關(guān)題目來做介紹,非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下!2023-01-01
Java9版本新特性同一個(gè)Jar支持多JDK版本運(yùn)行
這篇文章主要為大家介紹了Java9新版本的特性之同一個(gè)Jar支持多JDK版本運(yùn)行的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03
SpringBoot項(xiàng)目不占用端口啟動(dòng)的方法
這篇文章主要介紹了SpringBoot項(xiàng)目不占用端口啟動(dòng)的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08

