淺談MyBatis Plus主鍵設置策略
根據一次插入失敗報錯來瞭解下MyBatis Plus主鍵設置策略
今天學習使用MyBatis Plus,發現使用代碼生成器生成對應的實體類、Service和Mapper後,在保存數據時報錯
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
實體類City的主鍵是Integer類型的,在進行insert操作時,MyBatis Plus自動生成瞭一個Long類型的主鍵id,導致參數類型不匹配,出現上述錯誤
經過查看日志和調試發現,MyBatis最終調用BeanWrapper的setBeanProperty方法,通過反射執行最終的插入操作(增刪改查應該都是通過此處的反射,不過暫時隻調試瞭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即為我們想要保存到數據庫的實體信息(不帶ID信息),value為主鍵信息,此時主鍵值已經是一個Long類型的值,我們接著向上看value是哪裡傳過來的
setBeanProperty是一個私有方法,在本類調用,查詢看到set(PropertyTokenizer prop, Object value)方法調用瞭它
@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
查看報錯日志可以看到,BeanWrapper的上一層是MetaObject,我們找到MetaObject,看到在getValue方法中調用瞭BeanWrapper的set方法(BeanWrapper實現瞭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中的主鍵值也是上層調用傳入的,繼續根據錯誤日志向上看:MybatisDefaultParameterHandler
/** * <p> * 自定義元對象填充控制器 * </p> * * @param metaObjectHandler 元數據填充處理器 * @param tableInfo 數據庫表反射信息 * @param ms MappedStatement * @param parameterObject 插入數據庫對象 * @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; } /* 自定義元對象填充控制器 */ 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方法中調用瞭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()); } }
可以看到,此處根據IdType生成不同類型的主鍵id,IdType是一個枚舉類,定義瞭生成ID的類型
- AUTO 數據庫ID自增
- INPUT 用戶輸入ID
- ID_WORKER 全局唯一ID,Long類型的主鍵
- ID_WORKER_STR 字符串全局唯一ID
- UUID 全局唯一ID,UUID類型的主鍵
- NONE 該類型為未設置主鍵類型
當IdType的類型為ID_WORKER、ID_WORKER_STR或者UUID時,主鍵由MyBatis Plus的IdWorker類生成
ID_WORKER
調用IdWorker的getId()方法,生成一個與時間相關的主鍵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) { // 相同毫秒內,序列號自增 sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { // 同一毫秒的序列數已經達到最大 timestamp = tilNextMillis(lastTimestamp); } } else { // 不同毫秒內,序列號置為 1 - 3 隨機數 sequence = ThreadLocalRandom.current().nextLong(1, 3); } lastTimestamp = timestamp; return ((timestamp - twepoch) << timestampLeftShift) // 時間戳部分 | (datacenterId << datacenterIdShift) // 數據中心部分 | (workerId << workerIdShift) // 機器標識部分 | sequence; // 序列號部分 }
ID_WORKER_STR
將worker.nextId()的返回值轉化為字符串,和getId()方法相似
public static String getIdStr() { return String.valueOf(worker.nextId()); }
UUID
去除中劃線的UUID字符串
public static synchronized String get32UUID() { return UUID.randomUUID().toString().replace("-", ""); }
此時,已經基本可以確認,問題出在IdType的配置上,那麼這個IdType從哪裡獲取的呢?TableInfo中獲取的!
TableInfo是數據庫表反射信息實體類,此處由其他方法傳入的,查看日志:
at com.baomidou.mybatisplus.MybatisDefaultParameterHandler.populateKeys(MybatisDefaultParameterHandler.java:217) at com.baomidou.mybatisplus.MybatisDefaultParameterHandler.processBatch(MybatisDefaultParameterHandler.java:156)
通過日志可以看出,populateKeys()方法是在processBatch()方法中調用的,找到該方法
/** * <p> * 批量(填充主鍵 ID) * </p> * * @param ms * @param parameterObject 插入數據庫對象 * @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; }
下面對TableInfo生成的這段代碼做個說明
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:要保存到數據庫中的實體類信息
我在保存數據時,參數形式並不是Map類型的,所以直接跳轉到else中
tableInfo = TableInfoHelper.getTableInfo(parameterObject.getClass());
根據保存的實體類的類型去獲取數據庫表反射信息
我們看下getTableInfo()方法的實現
public static TableInfo getTableInfo(Class<?> clazz) { return tableInfoCache.get(ClassUtils.getUserClass(clazz).getName()); }
從tableInfoCache中獲取指定類型的數據庫反射信息。tableInfoCache是一個線程安全的私有靜態Map,主要用於存放類型和數據庫表的映射關系。斷點調試到此處,看下tableInfoCache的內容
可以看到,idType的值為ID_WORKER,即生成一個與時間相關的Long類型的id。在放入到tableInfoCache的時候就已經指定瞭idType的值。
查看TableInfoHelper的源碼可以得知,initTableInfo()方法負責initTableInfo的初始化
public synchronized static TableInfo initTableInfo(MapperBuilderAssistant builderAssistant, Class<?> clazz) { //檢測是否已存在 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 { // 兼容測試場景 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)); } /* 表結果集映射 */ if (table != null && StringUtils.isNotEmpty(table.resultMap())) { tableInfo.setResultMap(table.resultMap()); } List<TableFieldInfo> fieldList = new ArrayList<>(); List<Field> list = getAllFields(clazz); // 標記是否讀取到主鍵 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 轉換駝峰寫法為下劃線分割法, 如果已指定 TableField , 便不會執行這裡 */ fieldList.add(new TableFieldInfo(globalConfig, tableInfo, field)); } /* 字段列表 */ tableInfo.setFieldList(globalConfig, fieldList); /* * 未發現主鍵註解,提示警告信息 */ 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; }
當不存在主鍵註解時,會調用initFieldId()方法對主鍵屬性進行初始化
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; }
至此, 我們基本可以判斷是因為在實體類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來指定,默認是IdType.NONE(該類型為未設置主鍵類型),City表是通過數據庫的自增序列實現的,所以設置為AUTO
@TableId(type = IdType.AUTO) private Integer id;
然後測試,程序正常運行,保存數據成功。
擴展
對於tableInfo默認的idType值配置,可以看出用的是全局配置的idType,全局配置的值是在initTableInfo()方法中獲取的,有興趣的話可以去看看全局配置的實現,此處暫不深入瞭
GlobalConfiguration globalConfig; if (null != builderAssistant) { tableInfo.setCurrentNamespace(builderAssistant.getCurrentNamespace()); tableInfo.setConfigMark(builderAssistant.getConfiguration()); globalConfig = GlobalConfigUtils.getGlobalConfig(builderAssistant.getConfiguration()); } else { // 兼容測試場景 globalConfig = GlobalConfigUtils.DEFAULT; }
到此這篇關於淺談MyBatis Plus主鍵設置策略的文章就介紹到這瞭,更多相關MyBatis Plus主鍵設置內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- mybatis-plus 如何使用雪花算法ID生成策略
- Mybatis-Plus自動填充更新操作相關字段的實現
- springBoot集成mybatis 轉換為 mybatis-plus方式
- 已有的springcloud+mybatis項目升級為mybatis-plus的方法
- @insert mybatis踩坑記錄,實體接收前端傳遞的參數