1.githubä¸ç代ç å¦ä½è¿è¡
2.MyBatis自定义TypeHandler
3.Mybatis 码分OGNL导致的并发安全问题
4.Mybatisä¸example[Criteria]使ç¨
githubä¸ç代ç å¦ä½è¿è¡
ä»githubä¸çjava代ç æä¹ç¨eclipseè¿è¡
é¦å ï¼ä¸è½½githubä¸çJava代ç ï¼ç¶åæå¼eclipseï¼å¨å·¦è¾¹çå·¥ç¨æ ï¼é¼ æ å³é®éæ©importï¼ç¶åéæ©General-ExistingProjectsintoWorkspaceï¼ç¶åéçæ¨ä¸æ¥ä»£ç çè·¯å¾ä¸çæ件ï¼æ¥ä¸æ¥ä¸ç´nextå°±å¯ä»¥äºãé¦å å¨githubä¸æç´¢ä½ è¦ä¸è½½çæºç ä»åºï¼æè ä½ è¦æ¯ç¥éå°åç´æ¥æå¼å³å¯ï¼ï¼è¿é以mybatis为ä¾ç¹å»è¿å ¥mybatis-3ç页é¢ï¼ç¹å»CloneorDownloadå¤å¶é»è²çè·¯å²ï¼è¿å°±æ¯æ们ç¨Gitæ£åºç代ç è·¯å²ã
ä»githubå¯¼å ¥eclipse代ç çæ¹æ³ï¼eclipseä¸å®è£ gitï¼ç¹å»Help-InstallNewSoftware-addã
é¦å ï¼çREADME.mdï¼å¥½å¤é¡¹ç®é½æï¼æ没æ说æãå ¶æ¬¡ï¼çä½ ä¸è½½çå±äºä»ä¹ä»£ç ï¼å¯¹åºå°ç¸åºçå¼åç¯å¢ä¸ãç¶åï¼å°±æ¯å¨å¯¹åºçå¼åç¯å¢ä¸ç¼è¯ï¼èæ¬è¯è¨ç´æ¥æ¾å¨åºç¨ä¸ï¼ã
è°è¯´ä¸å¯ä»¥çãææææä½ ï¼å¨eclipseéæ°å»ºä¸ä¸ªprojectï¼å¨projecténewä¸ä¸ªclassï¼classååä¸ºä½ æºä»£ç ä¸publicclassxxx{ }ä¸çxxxçååãå¤å¶ï¼ç²è´´ï¼æ³¨æãè¦æç¨åºåæ¥ç»ä½ ç代ç æ¿æ¢æãç¶åå¨å·¦ä¾§ã
eclipseæ°å»ºjavaç¨åºçè¯ï¼é¦å éè¦å建ä¸ä¸ªjava项ç®å·¥ç¨ï¼å³é®èåï¼æ°å»ºjavaprojectï¼ä¼èªå¨æ·»å å·¥ç¨ç»æï¼ç¶åå³é®srcæ件夹ï¼éæ©classï¼åå ¥ä½ ç代ç ï¼æ主è¦æmainå½æ°ï¼ç¹å»è¿è¡çè¯ï¼å°±ä¼æ§è¡ä½ mainå½æ°çéé¢çè¯å¥ã
å¦ä½è¿è¡githubä¸ç代ç ä¸è½½ä»£ç ï¼å¨GitHubä¸æ¾å°æéç项ç®ï¼éæ©âCloneordownloadâæé®ï¼å¯ä»¥éæ©âDownloadZIPâç´æ¥ä¸è½½å缩å ï¼æè 使ç¨Gitå·¥å ·è¿è¡ä¸è½½ã解å代ç ï¼å°ä¸è½½çZIPå缩å 解åå°æ¬å°æå®çç®å½ä¸ãå®è£ 好ä¹åæç´¢GitHubå°è¾¾å®ç½å¨ä¸é¢æç´¢ãç»å½è´¦å·ä¹åç¹å»è¿éï¼å¨ç¹å»zipä¸è½½å缩æ件ã解åè¿ä¸ªæ件ä¹åï¼å«æ¾å¨Cçå³å¯ï¼ä¹åæä½shift+å³é®æå¼å½ä»¤çªå£ã
æmainä¸çå¾çãxmlççé½å¤å¶å°android-uiä¸å¯¹åºçä½ç½®ä¸ï¼åå¯¼å ¥å¯¹åºv4æè v7å 就好äºãæä¹å¸¸å¸¸å¨githubä¸ä¸è½½é¡¹ç®ï¼åºæ¬é½ä¼ç¢°å°è¿ä¸ªé®é¢ï¼é£ä¸é¢æè§ç¨eclipseçæ¯å°æ°ã
é¦å ï¼çREADME.mdï¼å¥½å¤é¡¹ç®é½æï¼æ没æ说æãå ¶æ¬¡ï¼çä½ ä¸è½½çå±äºä»ä¹ä»£ç ï¼å¯¹åºå°ç¸åºçå¼åç¯å¢ä¸ãç¶åï¼å°±æ¯å¨å¯¹åºçå¼åç¯å¢ä¸ç¼è¯ï¼èæ¬è¯è¨ç´æ¥æ¾å¨åºç¨ä¸ï¼ã
é¦å ï¼ä¸è½½githubä¸çJava代ç ï¼ç¶åæå¼eclipseï¼å¨å·¦è¾¹çå·¥ç¨æ ï¼é¼ æ å³é®éæ©importï¼ç¶åéæ©General-ExistingProjectsintoWorkspaceï¼ç¶åéçæ¨ä¸æ¥ä»£ç çè·¯å¾ä¸çæ件ï¼æ¥ä¸æ¥ä¸ç´nextå°±å¯ä»¥äºã
vscodeæ¬èº«æ¯ä¸ä¸ªæºä»£ç ç¼è¾å¨ï¼æ¬èº«å¹¶ä¸å ·å¤è¿è¡å端åºç¨çåè½ãæ£ç¡®çåæ³æ¯ï¼é¦å ç¨æ¬å°git客æ·ç«¯å°vue项ç®ä»githubå éå°æ¬å°ï¼ç¨vscodeæå¼ï¼å®æç¼è¾ã
ä»githubä¸é¢ä¸è½½ç项ç®æä¹è¿è¡ 1ãä¸è½½ä»£ç ï¼å¨GitHubä¸æ¾å°æéç项ç®ï¼éæ©âCloneordownloadâæé®ï¼å¯ä»¥éæ©âDownloadZIPâç´æ¥ä¸è½½å缩å ï¼æè 使ç¨Gitå·¥å ·è¿è¡ä¸è½½ã解å代ç ï¼å°ä¸è½½çZIPå缩å 解åå°æ¬å°æå®çç®å½ä¸ã2ãå®è£ 好ä¹åæç´¢GitHubå°è¾¾å®ç½å¨ä¸é¢æç´¢ãç»å½è´¦å·ä¹åç¹å»è¿éï¼å¨ç¹å»zipä¸è½½å缩æ件ã解åè¿ä¸ªæ件ä¹åï¼å«æ¾å¨Cçå³å¯ï¼ä¹åæä½shift+å³é®æå¼å½ä»¤çªå£ã
3ãé¦å ï¼å½ç¶æ¯è¦ä¸è½½åå®è£ 软件äºï¼æ¾ä¸å°è½¯ä»¶å¨åªéä¸çï¼çä¸å¾æå°åãï¼ï¼è¿ä¸ªæ¯è¾ç®åï¼è¿éå°±ä¸åç»è¯´ãæ们çé说ä¸æä¹ä½¿ç¨ãå®è£ 好åè¿è¡è½¯ä»¶ï¼ä½¿ç¨ä½ çGitHubè´¦å·ç»éã
4ãæmainä¸çå¾çãxmlççé½å¤å¶å°android-uiä¸å¯¹åºçä½ç½®ä¸ï¼åå¯¼å ¥å¯¹åºv4æè v7å 就好äºãæä¹å¸¸å¸¸å¨githubä¸ä¸è½½é¡¹ç®ï¼åºæ¬é½ä¼ç¢°å°è¿ä¸ªé®é¢ï¼é£ä¸é¢æè§ç¨eclipseçæ¯å°æ°ã
5ãè¦å¨æ¬å°è¿è¡GitHubä¸è½½çLanternï¼éè¦è¿è¡ä»¥ä¸æ¥éª¤ï¼ä»LanternçGitHubä»åºä¸ä¸è½½ææ°çæ¬çLanternã解å缩ä¸è½½çLanternå缩å ãå¨ç»ç«¯æå½ä»¤è¡ä¸è¿å ¥Lanternçç®å½ã
MyBatis自定义TypeHandler
MyBatis自定义TypeHandler1什么是TypeHandler
TypeHandler根据字面意思即为类型处理器
引用官方文档的描述:MyBatis在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时,都会用类型处理器将获取到的码分值以合适的方式转换成Java类型
MyBatis存在一些默认的类型处理器,可参考官方文档
2为什么要使用TypeHandler在开发过程中,当默认的TypeHandler无法满足需求时,例如遇到MyBatis不支持的数据类型或需要特殊处理的类型转换,便需要自己定制对应的TypeHandler
笔者会在下面的代码实现中完成如下几种情况的TypeHandler:
逗号分隔保存在数据库中的数据,在对应的Java类中为数组
自定义枚举
3如何自定义TypeHandlerMyBatis提供了接口org.apache.ibatis.type.TypeHandler和类org.apache.ibatis.type.BaseTypeHandler
官方文档给出的示例为继承BaseTypeHandler,笔者在这里也使用这种方式
先来观察一下官方的StringTypeHandler:
publicclassStringTypeHandlerextendsBaseTypeHandler<String>{ @OverridepublicvoidsetNonNullParameter(PreparedStatementps,inti,Stringparameter,JdbcTypejdbcType)throwsSQLException{ ps.setString(i,parameter);}@OverridepublicStringgetNullableResult(ResultSetrs,StringcolumnName)throwsSQLException{ returnrs.getString(columnName);}@OverridepublicStringgetNullableResult(ResultSetrs,intcolumnIndex)throwsSQLException{ returnrs.getString(columnIndex);}@OverridepublicStringgetNullableResult(CallableStatementcs,intcolumnIndex)throwsSQLException{ returncs.getString(columnIndex);}}方法名称和代码都简洁明了,观察可知,只需要完成四个方法的覆盖,即可实现自定义TypeHandler
3.1逗号分隔字符串转数组假设用户表t_user设计如下:
idusernametags1adminadmin,user对应的Java类为:
@Data@NoArgsConstructor@SuperBuilder(toBuilder=true)publicclassUser{ privateStringid;privateStringusername;privateString[]tags;}tags属性在数据库中用逗号分隔的字符串保存,但User类对应的属性为String数组
可以创建StringArrayTypeHandler来解决类型转换的问题:
publicclassStringArrayTypeHandlerextendsBaseTypeHandler<String[]>{ @OverridepublicvoidsetNonNullParameter(PreparedStatementpreparedStatement,inti,String[]strings,JdbcTypejdbcType)throwsSQLException{ preparedStatement.setString(i,StringUtils.join(strings,","));}@OverridepublicString[]getNullableResult(ResultSetresultSet,Strings)throwsSQLException{ returnconvert(resultSet.getString(s));}@OverridepublicString[]getNullableResult(ResultSetresultSet,inti)throwsSQLException{ returnconvert(resultSet.getString(i));}@OverridepublicString[]getNullableResult(CallableStatementcallableStatement,inti)throwsSQLException{ returnconvert(callableStatement.getString(i));}/***将查询值转换为数组**@paramvalue查询值,String*@return转换结果,String[]*/privateString[]convert(Stringvalue){ returnStringUtils.isEmpty(value)?newString[0]:value.split(",");}}3.2自定义枚举如何创建包含中文名称的枚举,可以参考MyBatis中使用Java类与枚举
先创建工具类用于根据code获取枚举实体:
publicclassValueNameEnumUtils{ privateValueNameEnumUtils(){ }publicstatic<EextendsValueNameEnum>EvalueOf(Class<E>enumClass,intvalue){ E[]enumConstants=enumClass.getEnumConstants();for(Ee:enumConstants){ if(e.getValue()==value){ returne;}}returnnull;}}和3.1中的情况不同,枚举的具体类型是不确定,所以我们要使用泛型的方式处理TypeHandler
创建ValueNameEnumTypeHandler:
publicclassValueNameEnumTypeHandler<EextendsValueNameEnum>extendsBaseTypeHandler<ValueNameEnum>{ privatefinalClass<E>type;publicValueNameEnumTypeHandler(Class<E>type){ if(type==null){ thrownewIllegalArgumentException("Typeargumentcannotbenull");}this.type=type;}}泛型虽然名之为泛,但在编译过程中实际会发生类型擦除
总之,对于泛型TypeHandler,我们需要声明一个用来标识具体类型的属性privatefinalClass<E>type和创建对应的构造函数publicValueNameEnumTypeHandler(Class<E>type)
接下来和3.1中的一致,重写四个方法:
publicclassValueNameEnumTypeHandler<EextendsValueNameEnum>extendsBaseTypeHandler<ValueNameEnum>{ privatefinalClass<E>type;publicValueNameEnumTypeHandler(Class<E>type){ if(type==null){ thrownewIllegalArgumentException("Typeargumentcannotbenull");}this.type=type;}@OverridepublicvoidsetNonNullParameter(PreparedStatementps,inti,ValueNameEnumparameter,JdbcTypejdbcType)throwsSQLException{ ps.setInt(i,parameter.getValue());}@OverridepublicEgetNullableResult(ResultSetrs,StringcolumnName)throwsSQLException{ intcode=rs.getInt(columnName);returnrs.wasNull()?null:valueOf(code);}@OverridepublicEgetNullableResult(ResultSetrs,intcolumnIndex)throwsSQLException{ intcode=rs.getInt(columnIndex);returnrs.wasNull()?null:valueOf(code);}@OverridepublicEgetNullableResult(CallableStatementcs,intcolumnIndex)throwsSQLException{ intcode=cs.getInt(columnIndex);returncs.wasNull()?null:valueOf(code);}/***根据枚举值返回枚举示例**@paramcode枚举值*@return枚举实例*/privateEvalueOf(intcode){ try{ returnValueNameEnumUtils.valueOf(type,code);}catch(Exceptionex){ thrownewIllegalArgumentException("Cannotconvert"+code+"to"+type.getSimpleName()+"bycodevalue.",ex);}}}完成上述代码直接启动,会抛出异常:Unabletofindausableconstructorforclasscn.houtaroy.springboot.common.MyBatis.handler.ValueNameEnumTypeHandler
产生异常的源码如下:
public<T>TypeHandler<T>getInstance(Class<?>javaTypeClass,Class<?>typeHandlerClass){ //未指定JavaType,此处为falseif(javaTypeClass!=null){ try{ Constructor<?>c=typeHandlerClass.getConstructor(Class.class);return(TypeHandler<T>)c.newInstance(javaTypeClass);}catch(NoSuchMethodExceptionignored){ //ignored}catch(Exceptione){ thrownewTypeException("Failedinvokingconstructorforhandler"+typeHandlerClass,e);}}try{ //此处抛出异常Constructor<?>c=typeHandlerClass.getConstructor();return(TypeHandler<T>)c.newInstance();}catch(Exceptione){ thrownewTypeException("Unabletofindausableconstructorfor"+typeHandlerClass,e);}}报错的原因直白,没有找到ValueNameEnumTypeHandler的构造函数
首先我们要了解下Java类构造函数的机制:如果定义了构造函数,则使用定义,否则默认生成空构造函数
在3.1中的StringArrayTypeHandler,我们没有定义构造函数,自动生成空构造函数,typeHandlerClass.getConstructor()不会抛出异常
但ValueNameEnumTypeHandler定义了一个构造函数ValueNameEnumTypeHandler(Class<E>type),且没有指定JavaType,typeHandlerClass.getConstructor()自然抛出异常
解决方法有两种:
创造空的构造函数
指定JavaType
笔者推荐第二种,因为第一种方式枚举类属性type会产生NPE(空指针异常),MyBatis官方也我们提供了注解@MappedTypes用于指定JavaType:
@MappedTypes(ValueNameEnum.class)publicclassValueNameEnumTypeHandler<EextendsValueNameEnum>extendsBaseTypeHandler<ValueNameEnum>{ //...}4如何使用TypeHandler在上一章节中,我们完成了编码实现自定义TypeHandler,但完成的TypeHandler还没办法进行使用,需要手动进行配置
有两种配置方式:局部使用和全局使用
以StringArrayTypeHandler举例:
4.1局部使用在ResultMap中使用:
<resultMapid="UserResultMap"type="cn.houtaroy.springboot.common.system.model.User"><idcolumn="id"property="id"/><resultcolumn="tags"property="tags"typeHandler="cn.houtaroy.springboot.extension.mybatis.handler.StringArrayTypeHandler"/></resultMap>在语句中使用:
updatet_usersettags=#{ tags,typeHandler=cn.houtaroy.springboot.extension.mybatis.handler.StringArrayTypeHandler}4.2全局使用使用配置文件指定handler包名:
@Data@NoArgsConstructor@SuperBuilder(toBuilder=true)publicclassUser{ privateStringid;privateStringusername;privateString[]tags;}0注意,此配置类型为String,只能配置一个包,推荐使用下面的方式
手写配置类:
@Data@NoArgsConstructor@SuperBuilder(toBuilder=true)publicclassUser{ privateStringid;privateStringusername;privateString[]tags;}1StringArrayTypeHandler不适合全局配置,它会在全部JavaType为String[]的属性上使用
5拓展阅读MyBatis3官方文档中TypeHandler内容:mybatis–MyBatis3|配置
网上搜索的在SpringBean声明周期中进行全局配置:Mybatis自定义全局TypeHander_chuobenggu的博客-CSDN博客
Mybatis OGNL导致的并发安全问题
Mybatis是一个轻量级半自动化ORM框架,通过xml描述符或注解将对象与SQL语句结合,码分实现面向对象与数据库映射的码分简化。其最大优势在于将应用程序与Sql语句解耦,码分php信息管理系统 源码Sql语句在xml文件中定义。码分预测大师指标源码
OGNL(Object-Graph Navigation Language)是码分Mybatis中广泛使用的表达式语言,用于设置和获取Java对象属性,码分执行列表投影和lambda表达式。码分灵活的码分OGNL表达式在Struts2等框架中应用时,也会引入可执行漏洞风险。码分
某公司使用Mybatis 码分3.2.3版本作为数据访问层。在线业务系统运行期间,码分linux lvs 源码解析出现并发安全问题。码分异常表现为随机出现,码分构造特定OGNL表达式时不会重现,具体异常堆栈信息显示List的溯源码可靠吗size()方法不可访问。此问题在测试环境未重现,占总调用次数的0.%。
编写模拟多线程并发环境下的测试代码以验证问题。并发测试代码执行后,伊卡洛斯ol 源码异常在预期的并发环境下重现。异常堆栈信息指向OgnlRuntime类无法访问java.util.Collections私有成员SingletonList。
问题关键在于method作为共享变量,即java.util.Collections$SingletonList.size()方法。在第一个线程允许调用method方法,第二个线程将其设为不可访问后,第一个线程再次调用时引发MethodFailedException异常。这是典型的并发同步问题。
OGNL 2.7版本已修复此问题,Mybatis在3.3.0版本中进行了修复升级。源码已直接嵌入mybatis包中,解决了并发访问共享资源导致的异常问题。
Mybatisä¸example[Criteria]使ç¨
æ们è¿ä¸ªç´æ¥çæºç çå¦ä½ç±exampleæ å°å°sqlè¯å¥
Stock.java
åªæ5个å±æ§ã
StockMapper.java:
æçåªçcountByExampleæ¥å£ï¼çååå°±ç¥éæ¯è¿åç»å®exampleåæ°çç»æ个æ°ã
åç
StockMapper.xml该æ¹æ³å®ç°ï¼
å¾ç®åç»å®åæ°ç±»åStockExample è¿åLongç±»å select count(*) from stock è¿å满足æ¡ä»¶çstock表ä¸æ°æ®æ°é
åç<include refid="Example_Where_Clause" />
foreach表示循ç¯ï¼åé符æ¯orï¼æè ï¼éç¹å¨äºæ°æ®éoredCriteria,æ们å»ç»å®åæ°StockExampleä¸å¯»æ¾oredCriteria
æ¯ä¸ä¸ªListå好åä¸é¢çéå对åºäºï¼ççå个Criteriaæ¯ä»ä¹
åçGeneratedCriteria
æ们åç°Criteriaéé¢å± ç¶è¿æä¸ä¸ªlist,æ们翻ä¸å»çå好ä¸ä¸é¢ä¸ä¸ªforeachéé¢è¿æä¸ä¸ªforeach对åºï¼é£æ¥ä¸æ¥ç 究Criterion
è¿å°±æ¯è¯¥ç±»ç声æï¼éé¢æè¿äºå¼ï¼è¿æ¶ååºè¯¥ç»åçä¹åxml第äºå±foreachå±æ§çäºï¼ç¥éè¿äºå¼æå¥ç¨
ä¸çæä»¬æ ¹æ®whenç¥éæåç±»æ åµï¼noValue,singleValue,betweenValue,listValue,对åºçæ¥è¯¢ä¸åç§æ åµ
1.åªæ¯å纯æ¯å¦é空
2.value=?å¤å°æ¥è¯¢
3.两è ä¹é´æ¥è¯¢
4.å¨listä¸æ¥è¯¢ã
æ以æ们æ建çæ¥è¯¢åæ°å°±æ¯criterionï¼çä¸ä¸ä¸é¢åç§åæ建å§
以ä¸åæ¯lGeneratedCriterialç±»ä¸æ¹æ³,addCriterionæ¯æçæçCriterionå å°Ctriteriaä¸
1.æ¯å¦é空ï¼
2.å¼æ¥è¯¢
public Criteria andIdEqualTo(Integer value) {
addCriterion("id =", value, "id");
return (Criteria) this;
}
3.两è ä¹é´
public Criteria andSaleNotBetween(Integer value1, Integer value2) {
addCriterion("sale not between", value1, value2, "sale");
return (Criteria) this;
}
4.list
public Criteria andVersionNotIn(List<Integer> values) {
addCriterion("version not in", values, "version");
return (Criteria) this;
}
å®ç»ï¼ç¸ä¿¡éè¿ä¸é¢çæºç åæï¼å¤§å®¶é½ç¥éå¦ä½æ建æ¥è¯¢æ¡ä»¶äºæã
æ»ç»
StockExample.java æé
ä¸ä¸ªåæ°
orderByClause:è¿åç»æåéæåºï¼å段+ç©ºæ ¼+acs/dcs(ååº/éåºï¼ä¾åcount acs
setOrderByClauseæ¹æ³æ³¨å ¥
distinctï¼true/false è¿åç»æå»ä¸å»éå¤,setDistinctæ³¨å ¥
oredCriteriaï¼å°±æ¯æ¥è¯¢åæ°ï¼ä¹åç讲äºå¥ææï¼æé æ¹æ³
æé Criteria
æ¶å·¥ã