目录
Hibernate是当前最流行的 O/R mapping框架。mybatis 是一种"Sql Mapping"的ORM实现。
Hibernate对数据库结构提供较为完整的封装,实现POJO和数据库表之间的映射,以及SQL的自动生成和执行。只需定义好POJO到数据库表的映射关系,即可通过 Hibernate 提供的方法完成持久层操作。甚至不需要对SQL的熟练掌握,Hibernate/OJB会根据制定的存储逻辑,自动生成对应的 SQL并调用JDBC接口加以执行。
mybatis的着力点,则在于POJO与SQL之间的映射关系。iBATIS并不会在运行期自动生成SQL执行。具体的SQL需要程序员编写,然后通过映射配置文件,将SQL所需的参数,以及返回的结果字段映射到指定POJO。使用mybatis提供的ORM机制,对业务逻辑实现人员而言,面对的是纯粹的Java对象,这一层与通过Hibernate 实现ORM而言基本一致,而对于具体的数据操作,Hibernate会自动生成SQL语句。相对 Hibernate而言,mybatis以SQL开发的工作量和数据库移植性上的让步,为系统设计提供更大的自由空间。
相同点:
Hibernate与MyBatis都是通过SessionFactoryBuider由XML配置文件生成SessionFactory,然后由SessionFactory 生成Session,最后由Session来开启执行事务和SQL语句。其中SessionFactoryBuider,SessionFactory,Session的生命周期都是差不多的。都支持JDBC和JTA事务处理。
hibernate 优势:
参考:
Hibernate3和MyBatis(iBatis)的执行效率比较
Hibernate一级缓存是Session缓存,利用好一级缓存就需要对Session的生命周期进行管理好。建议在一个Action操作中使用一个Session。一级缓存需要对Session进行严格管理。
Hibernate二级缓存是SessionFactory级的缓存。 SessionFactory的缓存分为内置缓存和外置缓存。内置缓存中存放的是SessionFactory对象的一些集合属性包含的数据(映射元素据及预定SQL语句等),对于应用程序来说,它是只读的。外置缓存中存放的是数据库数据的副本,其作用和一级缓存类似.二级缓存除了以内存作为存储介质外,还可以选用硬盘等外部存储设备。二级缓存称为进程级缓存或SessionFactory级缓存,它可以被所有session共享,它的生命周期伴随着SessionFactory的生命周期存在和消亡。
包括一级和二级。Mybatis默认查询顺序:二级缓存>一级缓存>数据库。
一级缓存:局部的Session会话级别的数据缓存。是为了短时间的一样的查询带来的资源浪费,MyBatis会在SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询时,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询。默认开启。
二级缓存:默认关闭。需要手动在SQL映射文件中,如UserMapper.xml配置
,然后初始化时会拿到缓存开启的配置,进行处理这样的元素节点:cacheElement(context.evalNode("cache"));
。作用:
相同点
Hibernate和Mybatis的二级缓存除了采用系统默认的缓存机制外,都可以通过实现你自己的缓存或为其他第三方缓存方案,创建适配器来完全覆盖缓存行为。
不同点
Hibernate的二级缓存配置在SessionFactory生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置是那种缓存。
MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。
Hibernate对查询对象有着良好的管理机制,用户无需关心SQL。所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示;MyBatis使用二级缓存时需要特别小心。如果不能完全确定数据更新操作的波及范围,避免Cache的盲目使用,否则会有脏数据。
参考:
Hibernate与MyBatis的对比
mybatis + MySQL 存储数据时遇到上面的报错,第一反应是去检查数据库表的DDL语句,以及查看待执行的 SQL 语句,但是两者都没有问题。最后才发现 mybatis 的 mapper.xml 文件里面的需要严格对应起来,即顺序。
<insert id="insert" parameterType="SomePO">
INSERT INTO order_data (
DemandOrderId, OrderFromWeight, OtherRequirementWeight, InterceptWeight)
VALUES (
#{po.demandOrderId},
#{po.orderFromWeight},
#{po.interceptWeight},
#{po.otherRequirementWeight})
insert>
比如上面的sampleMapper.xml 文件的写法有问题,
特殊符号 | 符号 | 意义 |
---|---|---|
< |
< | 小于 |
<= |
<= | 小于或等于 |
> |
> | 大于 |
>= |
>= | 大于或等于 |
<> |
<> | 不等于 |
& |
& | 逻辑与 |
' |
’ | 单引号 |
" |
" | 双引号 |
也可以使用<[CDATA[ ]]>
符号进行说明,将此类符号不进行解析,比如写 < > = 等:
MySQL like的写法
like concat('%',#{param},'%') 或者 like '%${param}%'
,推荐使用前者,可以避免SQL注入。
在mybatis-config.xml文件里面配置:
<typeAliases>
<package name="cn.caijiajia.campaign.common.domain" />
<package name="cn.caijiajia.campaign.common.form" />
<package name="cn.caijiajia.campaign.common.domain.vo" />
typeAliases>
方式一:
<sql id="Base_Column_List">
TargetID, UID, UName, UserId
sql>
<select id="searchByCondition" resultType="com.johnny.user.entity.CfgOrgUserTargetGmv">
SELECT
<include refid="Base_Column_List"/>
FROM cfg_orguser_targetgmv where 1=1
<if test="name != null and name !='' ">
and (UName like concat(concat("%", #{name}), "%") or UID like concat(concat("%", #{name}), "%") or
UserId like concat(concat("%", #{name}), "%"))
if>
<if test="pageIndex != null and pageSize != null ">
limit #{pageIndex}, #{pageSize}
if>
select>
对应的 Java mapper接口:
List
适用场景:
从数据库查询得到两个数据,是一一对应的k-v对形式的数据,现在希望把结果直接用Map接收,然后通过map.get(id)方便地获取name的值。
示例代码:
Map<String, SomeInfo> selectedSomeInfoMap = mgmContactsInfoMapper.getSelectedSomeInfoMap("csdn", uid);
对应的 mapper 文件的写法,这里面的mobile得是数据库的字段,SomeInfo的信息也得是直接或间接来自于数据库,查询条件可以有多个:
@MapKey("mobile")
Map<String, SomeInfo> getSelectedSomeInfoMap(@Param("appName") String appName, @Param("uid") String uid);
使用,注意这里需要对结果info做一下判空处理:
SomeInfo info = selectedSomeInfoMap.get(mobile);
Java 代码即 mapper 接口里面有一个接口需要返回一个 list 数据:
List
对应的 mapper.xml 文件:
<select id="getParentPathByPoid" resultMap="parentPathList">
SELECT ParentPath FROM BaoTuan_POI WHERE Poid = #{poid} AND DataStatus = 1 AND DataType='D'
select>
<resultMap id="parentPathList" type="java.lang.String">
<result column="ParentPath" property="parentPath" jdbcType="VARCHAR"/>
resultMap>
注意到上面的 jdbcType="VARCHAR"
如果写成 jdbcType="STRING"
,则会抛异常:
Caused by: java.lang.IllegalArgumentException: No enum constant org.apache.ibatis.type.JdbcType.STRING
遇到这种问题时,打开源码即可。根本就不需要像无头苍蝇一样去Google,需要学会去看源码,看error stack trace。
mapper.xml 文件可以配置有多个
标签,只需要确保 id 不同即可。
如下,parent_path是数据表的字段名,即列名;property是表实体POJO的属性名,一般在Java中属性名使用驼峰命名方式,但是MySQL表的字段名建议是下划线。这样mybatis通过反射得到 bean时,无法将查询到的表字段数据 set 到 bean 属性中。通过下面配置实现一一对应。
<resultMap id="parentPathList" type="java.lang.String">
<result column="parent_path" property="parentPath" jdbcType="VARCHAR"/>
resultMap>
CRUD 开发中,经常遇到一种场景就是依据待保存的数据的情况,实现插入新纪录或者更新记录。mybatis 模版代码如下:
<insert id="insertUpdate" parameterType="com.johnny.online.entity.User">
<selectKey keyProperty="count" resultType="int" order="BEFORE">
select count(*) from user where user_id= #{userId}
selectKey>
<if test="count > 0">
update user
set username= #{username}
where user_id= #{userId}
if>
<if test="count==0">
insert into user (user_id,username,userpass) values(#{userId},#{username},#{userpass})
if>
insert>
待进一步研究,不能实现:
<update id="insertUpdate" parameterType="CfgOrgUserTargetGmv">
insert into cfg_orguser_targetgmv(TargetID, UID, UName, PlatformUserId, PlatformProviderId, GMVQuarter1Target, GMVQuarter2Target,
GMVQuarter3Target, GMVQuarter4Target, GMVAnnualTarget, CreateUser, ModifyUser, UserGroupId, UserGroupName)
VALUES
(#{po.targetId}, #{po.uid}, #{po.saleName}, #{po.platformUserId}, #{po.platformProviderId}, #{po.gmvQuarter1Target}, #{po.gmvQuarter2Target},
#{po.gmvQuarter3Target}, #{po.gmvQuarter4Target}, #{po.gmvAnnualTarget}, #{po.createUser}, #{po.modifyUser}, #{po.userGroupId}, #{po.userGroupName})
on duplicate key UPDATE targetId = VALUES(TargetID)
update>
以MySQL数据库为例(不同的数据库,有些可能不支持)
常用的模糊查询有三种方法:
'%'#{name}'%'
或"%"#{name}"%"
,单引号或双引号都可以。示例:在userMapper.xml文件中新建映射sql的标签:
<select id="getUsersByFuzzyQuery" parameterType="User" resultType="User">
select <include refid="columns"/> from users
<where>
<if test="name != null">
name like "%"#{name}"%"
if>
<if test="phone != null">
and phone like concat(concat("%",#{phone}),"%")
if>
<if test="email != null">
<bind name="pattern" value="'%'+email+'%'"/>
and email like #{pattern}
if>
where>
select>
常规的SSM应用启动失败,报错:
Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.lang.RuntimeException: Error parsing Mapper XML. Cause: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for GeographyMapper.getByCode.
定位原因是在 mybatis 的 mapper.xml 文件里面有重复的 id 定义。
<select id="getByCode" resultType="Geography"
parameterType="java.lang.String">
select * from Geography where code = ${code}
select>
<select id="getByCode" resultType="Geography"
parameterType="java.lang.String">
select * from Geography where fullName = ${fullName}
select>
如下一条简单的 SQL 语句,期望返回一条记录,即一个 poid 最多有一条记录,但是现在测试环境的脏数据有两个记录(没有添加唯一索引),导致这个报错:
<select id="getParentPathByPoid" resultType="string">
SELECT ParentPath FROM POI WHERE Poid = #{poid} AND DataStatus = 1
select>
解决方法:
limit 1
,如果是 SQL server 数据源,则改为 SELECT top 1 ParentPath FROM POI WHERE Poid = #{poid} AND DataStatus = 1
;list
,然后过滤;mybatis 解析不了&&,mapper.xml 文件如下:
<update id="update" parameterType="Geography">
update Geography
<trim prefix="set" suffixOverrides=",">
<if test="code != null && code !=''">
code=#{code},
if>
<if test="name != null && name != ''">
name=#{name},
if>
trim>
where id= #{id}
update>
解决方法:使用正确的语法,用and替换&&
非常不建议。不过作为示例,还是记录一下。
<select id="archiveCoupons" statementType="CALLABLE">
CALL sp_coupon_archive(#{reserveDays});
select>
对应的 mapper 接口:
void archiveCoupons(@Param("reserveDays") int reserveDays);
定义一个sql标签:
<sql id="restrict_app">
(app_name = 'all' OR app_name LIKE CONCAT('%', #{appName}, '%'))
sql>
使用:
UPDATE user_task
SET status = 'VIEWED', updated_at = now()
WHERE `type` = #{type} AND
用于给mapper接口方法的参数指定一个别名,若接口只有一个参数则可以不用指定别名,List 参数除外。当有多个参数时一定要指定,否则 mybatis 映射不到对应的字段。另外:
使用@Param注解来声明参数时,使用 #{} 或 ${} 的方式都可以。
不使用@Param注解来声明参数时,必须使用 #{}方式。使用 ${} 会报错:org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'day' in 'class java.lang.String'
@Select("select column from table where userid = #{userid} ")
public int selectColumn(int userid);
那么#{}和${}
有什么区别呢?
#{} 表示一个占位符,通过 #{} 可以实现preparedStatement 向占位符中设置值,自动进行java 类型和 JDBC 类型转换;${}将传入的数据直接显示生成在sql中。#{} 可以有效防止 SQL 注入。
方法1:顺序传参法
public User selectUser(String name, int deptId);
<select id="selectUser" resultMap="UserResultMap">
select * from user
where user_name = #{0} and dept_id = #{1}
select>
#{}里面的数字代表你传入参数的顺序。
不建议使用,1. 参考上文,只能使用#{},且易导致SQL 注入;2. 顺序需要严格控制;3. 参数过大,影响阅读代码,不符合编码规范。
方法2:@Param注解传参法
public User selectUser(@Param("name") String name, @Param("deptId") int deptId);
略,有效防止 SQL 注入,但是仅仅推荐在参数不多的情况使用。
方法3:Java Bean传参法
public User selectUser(Map
<select id="selectUser" parameterType="com.johnny.demo.domain.User" resultMap="UserResultMap">
select * from user
where user_name = #{User.userName} and dept_id = #{User.deptId}
select>
#{}里面的名称对应的是 User类里面的成员属性。直观。
方法4:Map 传参法
public User selectUser(Map
<select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
select>
#{}里面的名称对应的是 Map里面的key名称。
这种方法适合传递多个参数,且参数易变能灵活传递的情况。但是 HashMap 的 key 存在 hard code 的问题。
实现批量更新on duplicate key UPDATE :
<insert id="duplicateUpdate" parameterType="User">
insert into user(id, uid, name, certificate_type, certificate_no, from_app_name, mobile, mobile_id_md5)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.id}, #{user.uid}, #{user.name}, #{user.certificateType}, #{user.certificateNo},#{user.fromAppName}, #{user.mobile}, #{user.mobileAndCertificateNoMD5})
foreach>
on duplicate key UPDATE
id = VALUES (id)
insert>
单个记录的更新on duplicate key UPDATE:
<update id="insertUpdate" parameterType="User">
insert into user(id, uid, name, certificate_type, certificate_no, from_app_name, mobile, mobile_id_md5)
VALUES
(#{id}, #{uid},#{name}, #{certificateType}, #{certificateNo}, #{fromAppName}, #{mobile}, #{mobileAndCertificateNoMD5})
on duplicate key UPDATE
id = VALUES (id)
update>
使用Mybatis Generator 这个maven插件来快速生成Dao类,mapper 配置文件和 Model 类。MyBatis Generator是MyBatis的代码生成器.可以自动查询数据库中的所有表,然后生成可以访问表的基础对象类型.解决了对数据库操作有最大影响的一些简单的CRUD增删改查操作,但是仍需要对联合查询和存储过程手写SQL语句和对象。
使用 mybatis-generator 步骤:
<plugin>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-maven-pluginartifactId>
<version>1.3.7version>
<configuration>
<configurationFile>${basedir}/src/main/resources/mybatis/generatorConfig.xmlconfigurationFile>
<verbose>trueverbose>
<overwrite>trueoverwrite>
configuration>
plugin>
如果不使用maven配置的话,也可以使用单元测试或者main入口函数的形式:
public static void main(String[] args) throws SQLException, IOException, InterruptedException, XMLParserException, InvalidConfigurationException {
List<String> warnings = new ArrayList<>();
boolean overwrite = true;
// 配置文件
File configFile = new File("generatorConfig.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);
}
<generatorConfiguration>
<classPathEntry location="mysql-connector-java-5.1.45-bin.jar"/>
<context id="default" targetRuntime="MyBatis3">
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="false"/>
commentGenerator>
<jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://:3306/?useUnicode=true&characterEncoding=UTF-8" userId="me" password="myPass">
jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
javaTypeResolver>
<javaModelGenerator targetPackage="com.johnny.onlinemall.domain" targetProject="../src/main/java">
<property name="constructorBased" value="false"/>
<property name="enableSubPackages" value="false"/>
<property name="immutable" value="false"/>
<property name="trimStrings" value="true"/>
javaModelGenerator>
<sqlMapGenerator targetPackage="com.johnny.onlinemall.mapper" targetProject="../src/main/resources">
<property name="enableSubPackages" value="false"/>
sqlMapGenerator>
<javaClientGenerator targetPackage="com.johnny.onlinemall.mapper" targetProject="../src/main/java" type="MIXEDMAPPER">
<property name="enableSubPackages" value=""/>
<property name="exampleMethodVisibility" value=""/>
<property name="methodNameCalculator" value=""/>
<property name="rootInterface" value=""/>
javaClientGenerator>
<table tableName="trans_log" delimitIdentifiers="true" delimitAllColumns="true"/>
context>
generatorConfiguration>
mvn mybatis-generator:generate -e
,-e参数是为了输出错误信息,方便排查问题。看到“BUILD SUCCESS”表示成功生成。
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="defaultExecutorType" value="REUSE"/>
<setting name="lazyLoadingEnabled" value="false"/>
<setting name="aggressiveLazyLoading" value="true"/>
<setting name="defaultStatementTimeout" value="25000"/>
settings>
<mappers>
<mapper resource="com/basessm/dao/UserMapper.xml"/>
mappers>
configuration>
官网:http://mp.baomidou.com/
简称MP,Mybatis增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
功能:
提供Mapper接口与配置文件中对应SQL的导航
编辑XML文件时自动补全
根据Mapper接口, 使用快捷键生成xml文件及SQL标签
ResultMap中的property支持自动补全,支持级联(属性A.属性B.属性C)
快捷键生成@Param注解
XML中编辑SQL时, 括号自动补全
XML中编辑SQL时, 支持参数自动补全(基于@Param注解识别参数)
自动检查Mapper XML文件中ID冲突
自动检查Mapper XML文件中错误的属性值
支持Find Usage
支持重构从命名
支持别名
自动生成ResultMap属性