2019年/11月/20日
不推荐mybatis
我一直不赞同大规模使用mybatis的,我知道没有包打天下的框架,但是我们不能宗教的把mybatis当成大部分项目的默认依赖 为什么国内这么流行呢?可能有人认为大厂在用啊,阿里在用啊,所以我们就用,这是一个典型没有自己独立思考的行为
持久的是什么?是公司的核心数据资产,和核心数据资产的交互组件一定要简单,可控,透明,所以我们应该始终使用弱封装框架,比如spring jdbc模板,当然还有很多其他方案
mybatis到底有什么问题?看看下面这段配置,这是开源工作流camunda的代码
<sql id="selectHistoricVariableInstanceByQueryCriteriaSql">
from ${prefix}ACT_HI_VARINST RES
<if test="authCheck.shouldPerformAuthorizatioCheck && !authCheck.revokeAuthorizationCheckEnabled && authCheck.authUserId != null">
<include refid="org.camunda.bpm.engine.impl.persistence.entity.AuthorizationEntity.authCheckJoinWithoutOnClause"/>
AUTH ON (AUTH.RESOURCE_ID_ in (RES.PROC_DEF_KEY_, '*'))
</if>
<if test="caseActivityIds != null && caseActivityIds.length > 0">
INNER JOIN ${prefix}ACT_HI_CASEACTINST HCAI
ON RES.ACT_INST_ID_ = HCAI.ID_
</if>
<where>
<if test="variableId != null">
RES.ID_ = #{variableId}
</if>
<if test="processInstanceId != null">
and RES.PROC_INST_ID_ = #{processInstanceId}
</if>
<if test="caseInstanceId != null">
and RES.CASE_INST_ID_ = #{caseInstanceId}
</if>
<if test="processDefinitionId != null">
and RES.PROC_DEF_ID_ = #{processDefinitionId}
</if>
<if test="processDefinitionKey !=null">
and RES.PROC_DEF_KEY_ = #{processDefinitionKey}
</if>
<if test="variableName != null">
and
<choose>
<when test="variableNamesIgnoreCase">
UPPER(RES.NAME_) = UPPER(#{variableName})
</when>
<otherwise>
RES.NAME_ = #{variableName}
</otherwise>
</choose>
${collationForCaseSensitivity}
</if>
<if test="variableNameLike != null">
and
<choose>
<when test="variableNamesIgnoreCase">
UPPER(RES.NAME_) like UPPER(#{variableNameLike})
</when>
<otherwise>
RES.NAME_ like #{variableNameLike}
</otherwise>
</choose>
ESCAPE ${escapeChar}
${collationForCaseSensitivity}
</if>
<if test="includeDeleted == false">
and (RES.STATE_ != 'DELETED' or RES.STATE_ is null)
</if>
<!-- processInstanceIds -->
<if test="processInstanceIds != null && processInstanceIds.length > 0">
and RES.PROC_INST_ID_ in
<foreach item="item" index="index" collection="processInstanceIds"
open="(" separator="," close=")">
#{item}
</foreach>
</if>
<!-- taskIds -->
<if test="taskIds != null && taskIds.length > 0">
and RES.TASK_ID_ in
<foreach item="item" index="index" collection="taskIds"
open="(" separator="," close=")">
#{item}
</foreach>
</if>
<!-- variableTypes -->
<if test="variableTypes != null && variableTypes.length > 0">
and lower(RES.VAR_TYPE_) in
<foreach item="item" index="index" collection="variableTypes"
open="(" separator="," close=")">
#{item}
</foreach>
</if>
<!-- executionIds -->
<if test="executionIds != null && executionIds.length > 0">
and RES.EXECUTION_ID_ in
<foreach item="item" index="index" collection="executionIds"
open="(" separator="," close=")">
#{item}
</foreach>
</if>
<!-- caseExecutionIds -->
<if test="caseExecutionIds != null && caseExecutionIds.length > 0">
and RES.CASE_EXECUTION_ID_ in
<foreach item="item" index="index" collection="caseExecutionIds"
open="(" separator="," close=")">
#{item}
</foreach>
</if>
<!-- caseActivityIds -->
<if test="caseActivityIds != null && caseActivityIds.length > 0">
and HCAI.CASE_ACT_ID_ in
<foreach item="caseActivityId" index="index" collection="caseActivityIds"
open="(" separator="," close=")">
#{caseActivityId}
</foreach>
</if>
<!-- activityInstanceIds -->
<if test="activityInstanceIds != null && activityInstanceIds.length > 0">
and RES.ACT_INST_ID_ in
<foreach item="item" index="index" collection="activityInstanceIds"
open="(" separator="," close=")">
#{item}
</foreach>
</if>
<if test="isTenantIdSet">
<if test="tenantIds != null && tenantIds.length > 0">
and RES.TENANT_ID_ in
<foreach item="tenantId" index="index" collection="tenantIds"
open="(" separator="," close=")">
#{tenantId}
</foreach>
</if>
<if test="tenantIds == null">
and RES.TENANT_ID_ is null
</if>
</if>
<!-- PLEASE NOTE: If you change anything have a look into the Execution, the same query object is used there! -->
<if test="queryVariableValue != null" >
<bind name="varTypeField" value="'VAR_TYPE_'"/>
<bind name="varPrefix" value="'RES.'"/>
<if test="queryVariableValue.valueConditions != null">
and
<include refid="org.camunda.bpm.engine.impl.persistence.entity.Commons.variableValueConditions"/>
</if>
</if>
<if test="authCheck.shouldPerformAuthorizatioCheck && authCheck.authUserId != null">
and (
(RES.EXECUTION_ID_ is not null
<include refid="org.camunda.bpm.engine.impl.persistence.entity.AuthorizationEntity.queryAuthorizationCheck"/>
) or RES.EXECUTION_ID_ is null
)
</if>
<include refid="org.camunda.bpm.engine.impl.persistence.entity.TenantEntity.queryTenantCheck" />
</where>
</sql>
你们认为它具备可维护行吗?具备可调试性吗?简洁透明吗?它操作的可是你企业核心命脉:数据
如果你使用过mybatis足够长的时间,你总结下你遇到的生产事故,反思一下,是否是mybatis的问题?2011年,淘宝发生了一次p1故障,就是这种xml配置的sql,在生产环境不停的物理删除数据。
我的核心观点
xml是一个配置语言,不是编程语言,mybatis是一个sql模板引擎,模板引擎可以用来渲染页面,做工具,但就是不适合操纵数据库,操纵数据库的行为太可怕,得足够敬畏。
所以我们对于数据核心逻辑,一定要借助于调试性和可视性都很强的逻辑语言来控制, spring最早都是用xml,现在演化为了配置bean,声明式代码,注意不是声明式配置文件
maven,ant这些用xml来控制逻辑的框架都在慢慢的被代码式,脚本式的gradle替代。
或者你可以关注什么叫做基础设施即代码 http://martinfowler.com/bliki/InfrastructureAsCode.html
对于代码声明式的数据库操作,业界有JOOQ的方案:
create.select(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, count())
.from(AUTHOR)
.join(BOOK).on(AUTHOR.ID.equal(BOOK.AUTHOR_ID))
.where(BOOK.LANGUAGE.eq("DE"))
.and(BOOK.PUBLISHED.gt(date("2008-01-01")))
.groupBy(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME)
.having(count().gt(5))
.orderBy(AUTHOR.LAST_NAME.asc().nullsFirst())
.limit(2)
.offset(1)
可以对比现在spring体系的实现形式
http
.authorizeRequests()
.antMatchers("/static/**", "/public/**", "/bower_components/**","/actuator/**").permitAll()
.antMatchers("/user/**").hasRole("USER")
.anyRequest()
.authenticated()
.and()
.csrf().disable()
.formLogin()
.defaultSuccessUrl("/")
.loginPage("/login")
.failureUrl("/login?error=1")
.permitAll()
.and().rememberMe().key("uniqueAndSecret").tokenValiditySeconds(86400)
.and()
.logout()
.deleteCookies("JSESSIONID")
.permitAll();
Groovy SQL
sql.eachRow("SELECT * FROM PROJECT") { rs ->
assertNotNull(rs.name)
assertNotNull(rs.URL)
}
Scala SQL,scalikejdbc
val name = "Alice"
// implicit session represents java.sql.Connection
val memberId: Option[Long] = DB readOnly { implicit session =>
sql"select id from members where name = ${name}" // don't worry, prevents SQL injection
.map(rs => rs.long("id")) // extracts values from rich java.sql.ResultSet
.single // single, list, traversable
.apply() // Side effect!!! runs the SQL using Connection
}
如果你的公司必须要使用mybatis,可以,那么掌握好分寸,阉割式使用,绝对不要写出上面这种xml代码,并配合好强大的单元测试,不然你一定会在生产出故障
网络有部分声音和我是相似的,但是每个人都有自己的风格,你可以继续阅读,总而言之,你需要独立思考,关注我后面的文章,我会批判Spring的@Transactional