ibatis官网:
http://ibatis.apache.org/
mybatis官网:
https://github.com/mybatis/mybatis-3/releases
帮助文档:
http://www.mybatis.org/mybatis-3/
一、mybatis基础
mybatis使用步骤:
1、最少导包
(mybatis、javassist、ognl、log4j-1.x(可选))(3.2.5)
(mybatis、log4j-1.x(可选))(3.2.8以上)
2、准备配置文件mybatis-config.xml(帮助文档的入门篇)
3、建表和实体类
4、配置sql映射文件或注解实体类(帮助文档的入门篇)
5、测试(帮助文档的入门篇)
6、mysql数据库sql语句后可以接; oracle不能加,所以都不建议加
mybatis-config.xml:
<properties resource="db.properties"/>
<environments default="development|work"> 开发模式|工作模式
<environment id="development"> id=default(id必须和default的值相同,否则打开连接异常)
<transactionManager type="JDBC|MANAGED"/> 事务管理
<dataSource type="POOLED|UNPOOLED"> 连接池
......
Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种方式:
SLF4J
Apache Commons Logging
Log4j 2
Log4j
JDK logging
具体选择哪个日志实现由MyBatis的内置日志工厂确定。
如果一个都未找到,日志功能就会被禁用。
添加log4j.properties配置文件,如果还是没有日志输出,在mybatis <typeAliases>前面添加:
<settings>
<setting name="logImpl" value="LOG4J" />
</settings>
对于自动增长的如mysql、sqlserver可以忽略主键列插入,如果也想获得刚插入记录的主键值:
<selectKey resultType="int" order="AFTER" keyProperty="hid">
SELECT LAST_INSERT_ID()|uuid_short()|uuid()|@@identity AS HID
select seq_stu.nextval from dual --序列BEFORE
</selectKey>
或者
<insert id="insertHusband" useGeneratedKeys="true" keyProperty="hid" parameterType="com.cssl.pojo.Husband">
insert into husband (name) values (#{name}) //sql可以写插入的id也可以不写
</insert>
int count = session.insert("com.cssl.insertHusband", h); //这里的count成功始终为1
System.out.println(h.getHid());
mybatis中解决数据库表与pojo中字段不一致的方法:
1、使用resultMap
<resultMap type="stu" id="stuMap">
<id property="sid" column="id"/>
<result property="sage" column="age"/>
</resultMap>
然后在查询中设置:
<select id="selectStudents" resultMap="stuMap">
select * from student
</select>
如果单表查询相同部分可以省略只配置不同处(包括id),多表连接查询的时候也不能省略id。
2、使用别名,as语法:select age as sage from student
3、也可以使用Map直接接收(List<Map<String,Object>>)(注意key值的大小写)
注意:这里的resultType使用的是别名,别名可以通过配置文件设置
<typeAliases>
<typeAlias alias="stu" type="com.cssl.pojo.Student"/>
<!-- 使用扫描包的方式,自动使用类名作为别名:Student|student(大小写都可) -->
<package name="com.cssl.pojo"/>
</typeAliases>
<mappers>
<!-- 通过package元素把指定包下面的所有Mapper接口注册,注意接口名和xml文件名相同 -->
<package name="com.cssl.mybatis.mapperinterface"/>
<!-- 通过mapper元素的resource属性可以指定一个相对于类路径的Mapper.xml文件 -->
<mapper resource="com/cssl/mybatis/mapper/UserMapper.xml"/>
<!-- 通过mapper元素的url属性可以指定一个通过URL请求到的Mapper.xml文件 -->
<mapper url="file:///E:/UserMapper.xml"/>
<!-- 通过mapper元素的class属性可以指定一个Mapper接口进行注册 -->
<mapper class="com.cssl.mybatis.mapperinterface.UserMapper"/>
</mappers>
如果使用占位符及jdbc属性文件方式配置数据库信息:
<properties resource="jdbc.properties"/>
或直接指定property
<properties resource="jdbc.properties">
<property name="jdbc.username" value="scott"/>
<property name="jdbc.password" value="tiger"/>
</properties>
MyBatis和Hibernate一样拥有缓存
注解:注意这里的#{属性名|map键名}不是表列名(其实是调用的get和set方法名)
@Insert("insert into user values(#{userName},#{password})")
@Delete("delete from user where userName=#{userName}")
@Update("update user set userName=#{userName}where id=#{id}")
@Select("select * from user where userName=#{userName}")
如果使用序列:mybatis自动返回最后插入的主键值
@Insert..
@SelectKey(before=true,resultType=int.class,keyProperty="id",statement="select seq_stu.nextval from dual")
对应xml:
<insert ...>
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select seq_stu.nextval | sys_guid() from dual
</selectKey>
insert into ...
</insert>
****************************************************************************************
二、动态sql
xml中多个参数的情况:
1、使用Map(匹配key,不配parameterType也会自动搜索键)
2、使用实体类(匹配get属性名,不配parameterType也会自动搜索属性)
3、方法直接带多个参数还可以使用 [0, 1, param1, param2] {0}|{param1}
如果param为对象#{param1.name}|#{0.name}
或者使用@Param("name")映射#{name}
4、注意有的字段允许为空(日期不能使用空字符串判断:date!='',字符串也不能与空格判断:name!=' ')
动态sql:(帮助文档动态SQL)
<select id="findActiveBlogLike"
parameterType="Blog" resultType="Blog">
SELECT sid,title FROM BLOG
<where>
<if test="state != null and state != ''">
state = #{state}
</if>
<if test="title != null"> (test="title!='%null%'")
AND title like #{title} |
AND title like CONCAT('%',#{title},'%')| (mysql)
AND title like CONCAT(CONCAT('%',#{title}),'%')|(oracle只能2个参数)
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
删除的时候需要注意条件不成立会删除所有数据!
<delete id="delete" parameterType="int">
delete from app t where (where 1=0)
<choose>
<when test="appId != null and appId != 0">
t.app_id = #{appId}
</when>
<otherwise>
1 = 0
</otherwise>
</choose>
</delete>
choose when 属于多重选择结构,只会进入其中一个条件
注意:#{}只能传递属性值,不能用于拼接列名、函数、序列或其他关键字(desc)
<if test="order!=null and order=='desc'">
order by id desc
</if>
<if test="order!=null and order=='asc'">
order by id
</if>
<if test="first!=null and last!=null">
limit #{first},#{last}
</if>
如果只是想直接在 SQL 语句中插入一个不改变的字符串(如列名、函数、序列或其他关键字),可以这样来使用:
ORDER BY ${columnName} ${desc}这里 MyBatis 不会修改或转义字符串。
注意:接受从用户输出的内容并提供给语句中不变的字符串,这样做是不安全的。这会导致潜在的 SQL 注入攻击,因此你不应该允许用户输入这些字段,或者通常自行转义并检查。
select ... where name like #{name} and id in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
如果除了list还有其他参数可以使用map封装:
map.put("list", list);
map.put("name", "admin");
<![CDATA[
bornDate < #{bornDate} | bornDate < #{bornDate}
]]>
Map中放Map:Map<String,Map>
<select id="selectInMap" resultType="Student">
select * from student where sid in
<foreach item="k" collection="ids.keys" (ids.values)
open="(" separator="," close=")">
${ids[k]} , #{ids[${k}]}
</foreach>
</select>
Map<String,Map> m = new HashMap<String,Map>();
Map<String,Object> ids = new HashMap<String,Object>();
ids.put("a", 1); -- ids.put("a",new ArrayList());
ids.put("b", 3); -- ids.put("b",new ArrayList());
ids.put("c", 5); -- ids.put("c",new ArrayList());
m.put("ids", ids);
bind绑定特殊符号:只能写在select下不能嵌入<if>里面
<bind name="pattern" value="'%'+_parameter.getTitle()(pojo)|_parameter.title(map|pojo)+'%'"/>
select * from student where sname like #{pattern}
*****************************************************************************************
三、关联映射
mybatis3.0添加了association和collection标签专门用于对多个相关实体类数据进行级联查询,但仍不支持多个相关实体类数据的级联保存和级联删除等操作。
插入关联表:
<insert id="insertWife" useGeneratedKeys="true" keyProperty="wid" parameterType="com.cssl.pojo.Wife">
insert into wife (name,h_id) values (#{name},#{husband.hid})
</insert>
1、关联映射:分嵌入、引入、继承等
<!-- 多对一 -->
嵌入式
<resultMap id="wifeandhusband" type="wife">
<id property="wid" column="wid"/>
<result property="wname" column="wname"/>
<association property="husband" column="w_hid" javaType="com.cssl.pojo.Husband">
<id property="hid" column="hid"/>
<result property="name" column="name"/>
</association>
</resultMap>
或者:引入式
<resultMap type="com.cssl.pojo.Wife" id="wifeandhusband">
<id property="wid" column="wid" />
<result property="wname" column="wname"/>
<association property="husband" column="w_hid" javaType="com.cssl.pojo.Husband" resultMap="husResult"></association>
</resultMap>
<resultMap id="husResult" type="com.cssl.pojo.Husband">
<id property="hid" column="hid"/>
<result property="name" column="name"/>
</resultMap>
或者:继承式(不支持多继承) 实际连表用的是husResult
<resultMap type="com.cssl.pojo.Wife" id="wifeandhusband">
<id property="wid" column="wid" />
<result property="wname" column="wname"/>
</resultMap>
<resultMap id="husResult" type="com.cssl.pojo.Wife" extends="wifeandhusband">
<association property="husband" column="w_hid" javaType="com.cssl.pojo.Husband">
...
</association>
</resultMap>
<!-- 一对多 -->
<resultMap id="husbandandwife" type="com.cssl.pojo.Husband">
<id property="hid" column="hid"></id>
<result property="name" column="name"></result>
<collection property="wifes" ofType="wife"> --ofType集合中的类型
<id property="wid" column="wid"></id>
<result property="wname" column="wname"></result>
</collection>
</resultMap>
<select id="selectWife" resultMap="wifeandhusband">
select w.*,h.* from wife w left join husband h on w.h_id=h.hid
</select>
关联映射的简化:
1、autoMapping="true"对于非关联关系中属性和列同名可以忽略配置result(id还需配置),对嵌套无效
2、<setting name="autoMappingBehavior" value="FULL"/> 对于关联关系起作用
只需要标签<association /> |<collection /> 嵌套id都可以忽略
3、<setting name="mapUnderscoreToCamelCase" value="true"/> 是否开启自动驼峰命名规则
hus_hid<->husHid(自动映射,无需配置)
注意:
1、关联查询一定要带有对象的id(主键),否则集合只会有一条记录存在(3.4版本无此bug)
2、查询无主表从表区别,也无需查询所有字段,都能封装对象
如:select h.name,h.age,w.wname from wife w left join husband h on h.hid=w.h_id
2、表连接中不同表有同名字段的时候:a和b都有name字段
<resultMap type="b" id="b">
<id property="bid" column="bid"/>
<result property="name" column="bname"/>
<association property="a" javaType="a">
<id property="id" column="aid"/>
<result property="name" column="aname"/>
</association>
</resultMap>
<select id="select" resultMap="b">
select a.id aid,a.name aname,b.id bid,b.name bname from a,b where a.id=b.id
</select>
3、采用实体类(视图)映射多表连接查询
4、使用Map映射多表连接查询返回List<Map<String,Object>>,不能有同名列
5、如果引入另一个配置文件可以使用namespace.id
将hibernate项目改成mybatis:事务通过过滤器控制
ThreadLocal<SqlSession> threadLocal = new ThreadLocal<SqlSession>();
/**
* 在同一线程获得的是同一个sqlSession
* @return
*/
public static SqlSession getSession() {
SqlSession session = threadLocal.get();
if (session == null) {
session = factory.openSession();
threadLocal.set(session);
}
return session;
}
//关闭
public static void closeSession() {
SqlSession session = threadLocal.get();
threadLocal.set(null);
if (session != null) {
session.close();
}
}
*****************************************************************************************
四、多对多关联映射、自动生成
多对多的关联都需要手动维护,包括关系的变化都需要手动删除和添加中间表!
可以手动维护或使用实体类维护关联关系
<!-- 手动维护关系 -->
<insert id="insertUsersRole" >
insert into users_role values(#{0},#{1})
</insert>
<!-- 实体类维护中间表关系 -->
<insert id="insertUsersRole" parameterType="UsersRoles">
insert into users_role values(#{users.userid},#{roles.rid})
</insert>
多对多映射:(同样可以使用视图映射)
A:Users 包含 Set<Roles>
<resultMap type="Users" id="usersMapper">
<id column="id" property="id"/>
<collection property="roles" column="ur_rid" ofType="Roles"/>
</resultMap>
或者:中间实体类映射
B:Users 包含 List<UsersRoles>
<resultMap type="Users" id="usersMapper">
<id column="id" property="id"/>
<collection property="roles" column="ur_rid" ofType="UsersRoles">
<association property="user" type="Users"/>
<association property="role" type="Roles"/>
</collection>
</resultMap>
discriminator:比如继承映射或者映射不同结果集
<resultMap type="pet" id="petMap">
<id column="pid" property="pid"/>
<result column="name" property="name"/>
<result column="love" property="love"/>
<discriminator javaType="int" column="type">
<case value="1" resultType="dog">
<result column="strain" property="strain"/>
</case>
<case value="2" resultType="penguin">
<result column="sex" property="sex"/>
</case>
</discriminator>
</resultMap>
<!-- petMap自动映射pet dog penguin -->
<select id="findPet" parameterType="int" resultMap="petMap">
SELECT * FROM pet where type = #{type}
</select>
自动生成:(反向工程)查generator文档
java -jar E:mybatis-generator-core-1.3.2.jar -configfile E:generator.xml -overwrite
代码生成:
List<String> warnings = new ArrayList<String>();
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);
终极Eclipse插件:
ibator_3.0.6.jar复制到eclipse的plugin|dropins目录下即可
注意:这里因为版本较低反向工程生成的sqlMapConfig中default和id值不同
<environments default="environments">
<environment id="accp">
...
注解映射关联关系:
@Results(value={@Result(property="id",column="id"),
@Result(property="username",column="username"),
@Result(property="password",column="password"),
@Result(property="account",column="id",javaType=Account.class,
one=@One(select="com.zjy.ibatis.annon.AccountMapper.findByCustomerId")),
@Result(property="orders",column="id",javaType=List.class,
many=@Many(select="com.zjy.ibatis.annon.OrderMapper.getOrdersByCustomerId")),
@Result(property="activities",column="id",javaType=List.class,
many=@Many(select="com.zjy.ibatis.annon.ActivityMapper.getActivitiesByCustomerId"))})
作业:
多对多映射
*****************************************************************************************
五、性能优化
<!-- 开启所有表的自动增长 -->
<setting name="useGeneratedKeys" value="true"/>
一、连表查询转成嵌套查询(懒加载)
<!-- 嵌套查询 -->
<select id="subWife" resultMap="wifemap">
select * from wife w
</select>
<resultMap type="wife" id="wifemap">
<association property="husband" column="w_hid"(外键) javaType="Husband" select="subHusband"/>
</resultMap>
<select id="subHusband" parameterType="int" resultType="com.cssl.pojo.Husband">
select * from Husband where hid=#{hid}
</select>
<!-- 一对多 -->
<resultMap type="Husband" id="husMapper">
<id column="hid" property="hid"/>
<result column="hname" property="hname"/>
<collection property="wifes" column="hid"(主键) ofType="Wife" select="selctWife"/>
</resultMap>
association(外键)或collection(主键)中的column表示把上述查出来的哪个字段值当作参数传给子查询
多对多:
<select id="select" resultMap="usersMapper">
select * from users
</select>
<resultMap type="Users" id="usersMapper">
<id column="uid" property="uid"/>
<collection property="roles" select="selectHW" column="userid"/>
</resultMap>
<select id="selectHW" resultMap="urMapper">
select * from users_roles where ur_uid=#{uid}
</select>
<resultMap type="UsersRole" id="urMapper">
<association property="role" column="ur_rid" select="selectRole"/>
</resultMap>
<select id="selectRole" resultType="Role">
select * from role where rid=#{rid}
</select>
这种嵌套的select语句的形式,是会导致所谓的“1+N问题”
这个问题,无论是association(多对一)元素还是collection(一对多)元素默认都会遇到
解决1+N问题:用到哪个对象才发对应的sql (默认都不是懒加载)
1、使用懒加载
配置:
<settingname="lazyLoadingEnabled" value="true"/>
只要用到对象,不管是否用到关联对象都发1+N,只有什么对象都不用才发一条sql
<setting name="aggressiveLazyLoading" value="false"/>
2、使用联接查询
left join ...
和hibernate懒加载的区别:
mybatis的懒加载只要用到了对象,即使没用到关联对象也会发sql查询关联对象
配置下面这个属性(按需加载)就可以不发sql查询关联对象
<setting name="aggressiveLazyLoading" value="false"/>
--加强懒加载,按需加载关联对象,前提是先开启懒加载
二、缓存:
一级缓存是默认开启不能关闭
mybatis中还默认开启了全局缓存(二级缓存)
缓存开启后session没有关闭执行同样的sql语句会使用缓存,如果参数改变,缓存就会失效。
缓存和返回类型是对象还是Map无关
如果要实现 mybatis 的二级缓存,一般来说有如下两种方式:
1. 采用 mybatis 内置的 cache 机制。
2. 采用三方 cache 框架, 比如ehcache, oscache 等等
<!-- mybatis-config中的 -->
<setting name="cacheEnabled" value="true"/> //如果设置为false二级缓存失效
二级缓存可以被所有session共享,实体对象也默认都没有使用缓存,开启后跨session也不发sql
如果要使用缓存,在xxxMapper.xml文件中添加 <cache />,这样对于当前xml的所有sql都会进行缓存。
注意:
1、采用内置缓存,对象及其关联对象都必须实现序列化
2、必须手动提交事务(提交的时候才会将数据存入二级缓存,自动提交是在方法调用完才提交,此时sql都发了没有利用到二级缓存)
3、还可以设置select是否缓存:<select usecache="true"> 每次从数据库获取数据,不使用二级缓存
4、还可以设置select是否刷新缓存:<select flushCache="true"> 每次都从数据库查询然后更新缓存数据,一级缓存都不用
A.采用mybatis内置的 cache 机制:只设置<cache />它将采用默认的行为进行缓存
映射文件中所有的select语句将被缓存usecache=true(参数相同)
映射文件中所有的insert、update和delete语句将更新缓存数据
eviction是缓存的淘汰算法,可选值有"LRU"、"FIFO"、"SOFT"、"WEAK",缺省值是LRU
flashInterval指缓存过期时间,单位为毫秒,缺省值为空,即只要容量足够,永不过期
size指缓存多少个对象,默认值为1024
readOnly是否只读,内置cache中如果为true,则所有相同的sql语句返回的是同一个对象(有助于提高性能,但并发操作同一条数据时,可能不安全),如果设置为false,则相同的sql,后面访问的是cache的clone副本。
所有这些属性都可以通过缓存元素的属性来修改,比如:
mapper:<cache eviction="LRU" flushInterval="60000" size="1024" readOnly="true"/>
使用:<cache-ref namespace="..." />
1、在不同的命名空间里共享同一个缓存配置,这样insert、update、delete操作也将清空引用命名空间的缓存
2、对于有相同命名空间的配置文件(不能直接写<cache/>),必须使用<cache-ref.../>
3、跨接口无法缓存(相同sql不缓存)
B.采用 ehcache 来实现 mybatis 的二级缓存
首先需要在mybatis的官网上下载相关jar包:https://code.google.com/p/mybatis/mybatis-ehcache-1.0.2.zip
mybatis-ehcache-1.x.x.jar
ehcache-core-2.x.x.jar
slf4j-api-1.x.x.jar
加入ehcache 的配置文件 ehcache.xml
sql映射文件可以配置:
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
或
<cache type="org.mybatis.caches.ehcache.LoggingEhcache" >
<property name="timeToIdleSeconds" value="3600"/><!--1 hour-->
<property name="timeToLiveSeconds" value="3600"/><!--1 hour-->
<property name="maxEntriesLocalHeap" value="1000"/>
<property name="maxEntriesLocalDisk" value="10000000"/>
<property name="memoryStoreEvictionPolicy" value="LRU"/>
</cache>
三、调用存储过程:
1、不支持MySQL的INOUT参数,必须使用CALLABLE
2、如果使用带有输出参数而且使用了查询的方式的存储过程不能开启缓存
配置mapper:(IN、OUT必须大写,输出参数必须带有jdbcType,也必须有OUT,resultTypek可以省略)
<select id="demo" statementType="CALLABLE" parameterType="map">
<![CDATA[
{call demo (#{p_in,mode=IN,jdbcType=INTEGER},#{p_out,mode=OUT,jdbcType=INTEGER})}
]]>
</select>
代码:
Map<String, Integer> param = new HashMap<String, Integer>();
param.put("p_in", 4);
param.put("p_out", 0);
//mysql5.0以上版本都不能使用值接收存储过程的返回值:
int returnValue = session.selectOne("com.cssl.demo", param);
或者映射方法:public void demo(Map<String, Object> map);
返回游标或ResultMap:
<select id="getNames" parameterType="map" statementType="CALLABLE">
{call PAGES(
#{id,jdbcType=INTEGER,mode=IN},
#{rs,jdbcType=CURSOR,mode=OUT})}
</select>
可以直接使用Map或ResultMap对应CURSOR
Map params = new HashMap();
params.put("id", 11);
params.put("rs", new ArrayList<Map<String, Object>>());
ResultMap:
<select id="getNames" parameterType="map" statementType="CALLABLE">
{ call PAGES(
#{id,jdbcType=INTEGER,mode=IN},
#{rs,jdbcType=CURSOR,mode=OUT,resultMap=cursorMap}) }
</select>
<resultMap type ="java.util.HashMap" id= "cursorMap">
<result column ="plan_code" property="plan_code" />
...
</resultMap >
补充:
分页插件PageHelper: 见插件文档