public Class Role {
private Long id;
private String roleName;
private Strign note;
/** setter and getter **/
}
XML方式创建映射器
它包括一个接口,一个XML。
映射器接口 RoleMapper.java
public interface RoleMapper {
public int insertRole(Role role);
public int deleteRole(Long id);
public int updateRole(Role role);
public Role getRole(Long id);
public List<Role> getRoles(String roleName)
}
映射XML RoleMapper.xml
<mapper namespace="com.mybatis.mapper.RoleMapper">
<insert id="insertRole" parameterType="com.mybatis.po.Role">
insert into t_role (role_name, note) values (#{roleName},#{note})
</insert>
<delete id="deleteRole" parameterType="Long">
delete from t_role where id=#{id}
</delete>
<update id="updateRole" parameterType="com.mybatis.po.Role">
update t_role set role_name=#{roleName} , note=#{note}) where id=#{id}
</update>
<select id="getRole" parameterType="Long" resultType="com.mybatis.po.Role">
select id, role_name as roleName, note from t_role where id=#{id}
</select>
<select id="getRoles" parameterType="String" resultType="com.mybatis.po.Role">
select id, role_name as roleName, note from t_role where role_name like concat('%', #{roleName} , "%")
</select>
</mapper>
mybatis-config.xml
创建 SessionFactory 的工具类
创建 SessionFactory 使用静态单例模式
public class SqlSessionFactoryUtils {
private static SqlSessionFactory sqlSessionFactory=null;
static{
try{
Reader reader= Resources.getResourceAsReader("mybatis-config.xml");
sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
}catch (Exception e){
e.printStackTrace();
}
}
public static SqlSession getSession(){
return sqlSessionFactory.openSession();
}
}
创建 DAO 类
SqlSession sqlSession = null;
try {
sqlSession = SqlSessionFactoryUtils.getSession();
RoleMapper mapper = sqlSession.getMapper(getSession.class);
Role role = mapper.getRole(1L);
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class c3p0DataSourceFactory extends UnpooledDataSourceFactory{
public c3p0DataSourceFactory(){
this.dataSource=new ComboPooledDataSource();
}
}
<select id="countUserByFirstName" parameterType="string" resultType="int">
select count(*) total from t_user where user_name like concat(#{firstName}, %)
</select>
<select id="getRole" parameterType="Long" resultType="com.mybatis.po.Role">
select id, role_name as roleName, note from t_role where id=#{id}
</select>
映射接口
public class Role {
private Long id;
private String roleName;
private String note;
}
public List<Role> findRolesByMap(Map<String,Object> param);
<select id="findRolesByMap" parameterType="map" resultType="com.mybatis.po.Role">
select * from user where role_name like concat('%',#{roleName},'%') and note = #{note}
</select>
参数名 u_name 和 u_sex 是 Map 的 key。
使用这个SQL映射。
RoleMapper mapper = sqlSession.getMapper(RoleMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("roleName", "1");
map.put("note", "1");
List<Role> list = mapper.findRolesByMap(map);
for(Role role : list) {
System.out.println(role.getId());
}
一般不推荐这种方式。
使用注解传递多个参数
使用map,可读性差。可以使用注解的方式
public List<Role> findRolesByAnnotation(@Param("roleName") String roleName, @Param("note") String note);
<select id="findRolesByMap" resultType="com.mybatis.po.Role">
select * from user where role_name like concat('%',#{roleName},'%') and note = #{note}
</select>
不用指定 parameterType,让 MyBatis自动探索就可以。
使用 Java Bean 传递多个参数
先定义个参数的POJO
public class RoleParams {
private String roleName;
private String note;
// getter and setter
}
把接口方法定义成
public List<Role> findRolesByBean(RoleParams roleParam) ;
<select id="findRolesByMap" parameterType="com.mybatis.test.param.RoleParams" resultType="com.mybatis.po.Role">
select * from user where role_name like concat('%',#{roleName},'%') and note = #{note}
</select>
使用这个SQL
RoleMapper mapper = sqlSession.getMapper(RoleMapper.class);
RoleParams params = new RoleParams();
params.setRoleName("1");
params.setNote("1");
List<Role> list = mapper.findRolesByBean(params);
混合使用
有时候,可以混合使用JavaBean和注解的方法
public List<Role> findByMix(@Param("params") RoleParams roleParam, @Param("page") PageParams pageParam) ;
<select id="findRolesByMap" resultType="com.mybatis.po.Role">
select * from user where role_name like concat('%',#{params.roleName},'%') and note = #{params.note}
limit #{page.start} , #{page.limit}
</select>
@Select("select * from user where id = #{id}")
User findById(@Param("id") long id);
@Select("select * from user where name = #{name}")
User findByName(@Param("name") String name);
@Select("select * from user where email = #{email}")
User findByEmail(@Param("email") String email);
SQL几乎一样,只是查询字段不一样。
可以只写这样一个方法
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);
其中 ${column} 会被直接替换,而 #{value} 会被使用 ? 预处理。
可以像下面这样来达到上面的功能
User user1 = userMapper.findByColumn("id", 1L);
User user2 = userMapper.findByColumn("name", "wang");
User user3 = userMapper.findByColumn("email", "wang@test.com");
public class Person {
private Integer id;
private String name;
private Integer age;
// 个人身份证关联
private Idcard card;
// getter and setter
}
Idcard类
public class Idcard {
private Integer id;
private String code;
// getter and setter
}
创建两张表对应的映射文件 IdCardMapper.xml 和 PersonMapper.xml。
IdCardMapper.xml
<mapper namespace="com.dao.IdCardDao">
<select id="selectCodeById" parameterType="Integer" resultType= "com.po.Idcard">
select * from idcard where id=#{id}
</select>
</mapper>
PersonMapper.xml
PersonMapper.xml 文件中以 3 种方式实现 根据 id 查询个人信息 的功能
<mapper namespace="com.dao.PersonDao">
<!-- 一对一根据id查询个人信息:级联查询的第一种方法(嵌套查询,执行两个SQL语句)-->
<resultMap type="com.po.Person" id="cardAndPerson1">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<!-- 一对一级联查询-->
<association property="card" column="idcard_id" javaType="com.po.Idcard" select="com.dao.IdCardDao.selectCodeByld"/>
</resultMap>
<select id="selectPersonById1" parameterType="Integer" resultMap="cardAndPerson1">
select * from person where id=#{id}
</select>
<!--对一根据id查询个人信息:级联查询的第二种方法(关联查询,执行一个SQL语句)-->
<resultMap type="com.po.Person" id="cardAndPerson2">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<!-- 一对一级联查询-->
<association property="card" javaType="com.po.Idcard">
<id property="id" column="idcard_id"/>
<result property="code" column="code"/>
</association>
</resultMap>
<select id="selectPersonById2" parameterType="Integer" resultMap= "cardAndPerson2">
select p.*, ic.code from person p, idcard ic where p.idcard_id=ic.id and p.id=#{id}
</select>
<!-- 一对一根据id查询个人信息:连接查询(使用POJO存储结果)-->
<select id="selectPersonById3" parameterType="Integer" resultType= "com.pojo.SelectPersonById">
select p.*, ic.code from person p, idcard ic where p.idcard_id = ic.id and p.id=#{id}
</select>
</mapper>
第三种方法,需要再创建一个POJO
public class SelectPersonById {
private Integer id;
private String name;
private Integer age;
private String code;
// getter and setter
}
然后创建映射器接口
public interface IdCardDao {
public Idcard selectCodeById(Integer i);
}
public interface PersonDao {
public Person selectPersonById1(Integer id);
public Person selectPersonById2(Integer id);
public SelectPersonById selectPersonById3(Integer id);
}
最后使用映射器取得结果
Person p1 = personDao.selectPersonById1(1);
Person p2 = personDao.selectPersonById1(2);
selectPersonById p3 = personDao.selectPersonById1(3);
<select id="selectUserByIf" resultType="com.po.MyUser" parameterType="com.po.MyUser">
select * from user where 1=1
<if test=" uname != null and uname != '' ">
and uname like concat ( '%' , #{uname} , '%' )
</if >
<if test=" usex != null and usex !='' ">
and usex = #{usex}
</if >
</select>
choose when otherwise 元素
类似于 switch case default。
<select id="selectUserByChoose" resultType="com.po.MyUser" parameterType= "com.po.MyUser">
select * from user where 1=1
<choose>
<when test=" uname != null and uname !='' ">
and uname like concat('%',#{uname},'%')
</when>
<when test=" usex != null and usex != '' ">
and usex=#{usex}
</when>
<otherwise>
and uid > 10
</otherwise>
</choose>
</select>
where 元素
可以看到,前面 加上了 where 1=1,如果不加上 1=1 ,这个SQL 语句语法是不正确的。
比如
select * from user where and uname like concat('%',#{uname},'%')
可以用where 元素来处理SQL来达到预期的效果。
当where元素里面的条件成立,才会加入 where 这个SQL关键字组装的SQL里面,否则不加入。
MyBatis 将智能处理。如果所有的条件都不满足,那么 MyBatis 就会查出所有的记录,如果输出后是以 and 开头的,MyBatis 会把第一个 and 忽略。
<select id="selectUserByWhere" resultType="com.po.MyUser" parameterType="com.po.MyUser">
select * from user
<where>
<if test="uname != null and uname ! = ''">
and uname like concat('%',#{uname},'%')
</if>
<if test="usex != null and usex != '' ">
and usex=#{usex}
</if >
</where>
</select>
trim 元素
trim 元素可以去掉一些特殊的SQL语句。
下面的形式,就可以替换 where 元素。
<select id="selectUserByTrim" resultType="com.po.MyUser"parameterType="com.po.MyUser">
select * from user
<trim prefix="where" prefixOverrides = "and | or">
<if test="uname!=null and uname!=''">
and uname like concat('%',#{uname},'%')
</if>
<if test="usex!=null and usex!=''">
and usex=#{usex}
</if>
</trim>
</select>
<select id="selectUserByBind" resultType="com.po.MyUser" parameterType= "com.po.MyUser">
<!-- bind 中的 uname 是 com.po.MyUser 的属性名-->
<bind name="paran_uname" value="'%' + uname + '%'"/>
select * from user where uname like #{paran_uname}
</select>
这样就绑定了新的变量 paran_uname ,可以在SQL的其他地方使用。
MyBatis 事物
增,删,改 数据,需要 sqlSession.commit()
try (SqlSession session = sqlSessionFactory.openSession()) {
RoleMapper mapper = sqlSession.getMapper(RoleMapper.class);
Role role = new Role(xxx, xxxx);
int result = mapper.addRole(role);
sqlSession.commit(); // 手工提交
}
MyBatis
MyBatis 本是 Apache 的一个开源项目——iBatis,2010 年这个项目由 Apache Software Foundation 迁移到了 Google Code,并且改名为 MyBatis。
MyBatis 使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的 POJO 映射成数据库中的记录。
Hibernate 和 MyBatis 的区别
总的来说
所以对于性能要求不太苛刻的系统,比如管理系统、ERP 等推荐使用 Hibernate。 对于性能要求高、响应快、灵活的系统则推荐使用 MyBatis。
1. MyBatis 的工作流程
简单的加上解释,包括跟Hibernated的对照。
2. MyBatis 的核心组件
MyBatis 的核心组件分为 4 个部分。
SqlSessionFactoryBuilder(构造器) 它会根据配置或者代码来生成 SqlSessionFactory。
SqlSessionFactory(工厂接口) 依靠它来生成 SqlSession。
SqlSession(会话) 一个既可以发送 SQL 执行返回结果,也可以获取 Mapper 的接口。
SQL Mapper(映射器) 它由一个 Java 接口和 XML 文件(或注解)构成,需要给出对应的 SQL 和映射规则。它负责发送 SQL 去执行,并返回结果。
注意,无论是映射器还是 SqlSession 都可以发送 SQL 到数据库执行。
SqlSessionFactory
在 MyBatis 中,既可以通过读取配置的 XML 文件的形式生成 SqlSessionFactory,也可以通过 Java 代码的形式去生成 SqlSessionFactory。 推荐采用 XML 的形式。
从 XML 中构建 SqlSessionFactory
XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)。后面会再探讨 XML 配置文件的详细内容
创建 SqlSessionFactory
SqlSession
SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。 你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
具体而言,它的作用有 3 个
SqlSession是一个门面接口,在MyBatis中,真正干活的是Executor。
映射器
映射器是 MyBatis 中最重要、最复杂的组件,它由 一个接口 和 对应的 XML 文件或注解 组成。 可以通过 XML 定义,也可以通过注解定义。
用 XML 定义映射器分为两个部分: 接口和 XML。
先定义一个映射器接口
用 XML 方式创建映射器
在 mybatis-config.xml 中导入
我们并没有配置 SQL 执行后和 Blog 的对应关系,它是如何映射的呢? 其实这里采用的是一种被称为自动映射的功能,MyBatis 在默认情况下提供自动映射,只要 SQL 返回的列名能和 POJO 对应起来即可。 MyBatis 就可以把 SQL 查询的结果通过自动映射的功能映射成为一个 POJO。
在 mybatis-config.xml 中导入
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
如果 注解 和 XML 方式同时定义时,XML 方式将覆盖掉注解方式,所以 MyBatis 官方推荐使用的是 XML 方式。
MyBatis执行SQL的两种方式
有了映射器就可以通过 SqlSession 发送 SQL 了。
selectOne 表示查询并且只返回一个对象。 参数
com.mybatis.mapper.RoleMapper.getRole
是一个命名空间加上SQL id 组合而成。它定位了一个SQL。 这样,MyBatis就可以找到一个SQL。如果MyBatis中只要一个id 叫 getRole 的SQL,那么不需要加命名空间。这是 MyBatis 前身 iBatis 所留下的方式。
SqlSession 还可以获取 Mapper 接口,通过 Mapper 接口发送 SQL。
通过上面,可以看到,MyBatis 存在的两种发送 SQL 的方式。 一种用 SqlSession 直接发送,另外一种通过 SqlSession 获取 Mapper 接口再发送。 目前使用 Mapper 接口编程已成为主流。
生命周期
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。 SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次。 最简单的就是使用单例模式或者静态单例模式。
如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection 对象)。 你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。
每个线程都应该有它自己的 SqlSession 实例。 SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中
下面的示例就是一个确保 SqlSession 关闭的标准模式。 Java 7 try-with-resources
在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。
Mapper 是一个接口,它由 SqlSession 所创建,所以它的最大生命周期至多和 SqlSession 保持一致。 由于 SqlSession 的关闭,它的数据库连接资源也会消失,所以它的生命周期应该小于等于 SqlSession 的生命周期。
映射器实例并不需要被显式地关闭。 因此,最好将映射器放在方法作用域内。就像下面的例子一样
另外,依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。
MyBatis 的实际开发例子
Role.java
它包括一个接口,一个XML。
映射器接口 RoleMapper.java
映射XML RoleMapper.xml
创建 SessionFactory 使用静态单例模式
3. MyBatis 配置
MyBatis的配置并不复杂,但是需要注意的是,MyBatis的配置项的顺序不能颠倒。否则启动会报异常。
properties 属性
properties 属性可以给系统配置一些运行参数,可以放在 XML 文件 或者 properties 文件中
设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。比如:
使用 properties 文件 比如,我们创建一个文件 jdbc.properties 放到 classpath 的路径下
在 MyBatis 中通过
<properties>
的属性 resource 来引入 properties 文件。也可以按
${database.username}
的方法引入 properties 文件的属性参数到 MyBatis 配置文件中。settings 设置
在 MyBatis 中 settings 是最复杂的配置,它能深刻影响 MyBatis 底层的运行。 但是在大部分情况下使用默认值便可以运行,所以在大部分情况下不需要大量配置它,只需要修改一些常用的规则即可,比如自动映射、驼峰命名映射、级联规则、是否启动缓存、执行器(Executor)类型等。
cacheEnabled 所有映射器中配置缓存的全局开关
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 在特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。
aggressiveLazyLoading 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载,反之,每种属性将会按需加载。
useGeneratedKeys 对于支持自动生成记录主键的数据库,如:MySQL,SQL Server,此时设置useGeneratedKeys参数值为true,在执行添加记录之后可以获取到数据库自动生成的主键ID。 实际上,在settings元素中设置useGeneratedKeys是一个全局参数,但是只会对接口映射器产生影响,对xml映射器不起效。
此时,在接口映射中添加记录之后将返回主键ID。
请注意如果此时在接口映射器中又明确设置了useGeneratedKeys参数,那么注解映射器中的useGeneratedKeys参数值将覆盖settings元素中设置的全局useGeneratedKeys参数值。
在settings元素中设置的全局useGeneratedKeys参数对于xml映射器无效。 如果希望在xml映射器中执行添加记录之后返回主键ID,则必须在xml映射器中明确设置useGeneratedKeys参数值为true。
在xml映射器中配置useGeneratedKeys参数
autoMappingBehavior 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射 PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 默认 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。
defaultExecutorType 配置默认的执行器。 SIMPLE 是普通的执行器。 默认 REUSE 会重用预处理语句(prepared statements)。 BATCH 执行器将重用语句并执行批量更新。
defaultFetchSize 设置数据库驱动程序默认返回的条数限制。
mapUnderscoreToCamelCase 是否开启自动驼峰命名规则映射,即从数据库列名 A_COLUMN 到 Java 属性名 aColumn 的类似映射 默认是false。
localCacheScope 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlScssion 的不同调用将不会共享数据。
类型别名 typeAliases
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。 MyBatis 别名不区分大小写。
系统自动初始化了一些别名 比如,string,long,integer,object,map,hashmap,list,arraylist,iterator
自定义别名
MyBatis 还支持扫描别名。 比如上面的两个类都在包 com.mybatis.po 之下,那么就可以定义为
这样 MyBatis 将扫描这个包里面的类,将其第一个字母变为小写作为其别名。 比如类 Role 的别名会变为 role,而 User 的别名会变为 user。
使用这样的规则,有时候会出现重名。 比如
com.mybatis.po.Role
这个类,MyBatis 还增加了对包com.mybatis.po2
的扫描,com.mybatis.po2
里也存在User类,那么就会出现异常。 这个时候可以使用 MyBatis 提供的注解 @Alias("user2") 进行区分。typeHandler 类型处理器
MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。
在 typeHandler 中,分为 jdbcType 和 javaType,其中 jdbcType 用于定义数据库类型,而 javaType 用于定义 Java 类型,那么 typeHandler 的作用就是承担 jdbcType 和 javaType 之间的相互转换。
和别名一样,在 MyBatis 中存在 系统定义 typeHandler 和自定义 typeHandler。
MyBatis 会根据 javaType 和数据库的 jdbcType 来决定采用哪个 typeHandler 处理这些转换规则。 系统提供的 typeHandler 能覆盖大部分场景的要求。
MyBatis 系统已经创建好默认的 typeHandler。在大部分的情况下无须显式地声明 jdbcType 和 javaType,或者指定对应的 typeHandler 来实现数据类型转换,因为 MyBatis 系统会自己探测。
自定义 typeHandler
需要去实现接口 typeHandler,或者继承 BaseTypeHandler。 实际上,BaseTypeHandler 实现了 typeHandler 接口。
定义的 typeHandler 泛型为 String,我们要把数据库的数据类型转化为 String 型。
然后要配置 typeHandler ,配置完成后,系统会读取它。
注册后,当 jdbcType 和 javaType 能与 MyTypeHandler 对应的时候,它就会启动 MyTypeHandler。
还可以显式启用 typeHandler,一般而言启用这个 typeHandler 有两种方式。
或者
要么指定了与自定义 typeHandler 一致的 jdbcType 和 javaType,要么直接使用 typeHandler 指定具体的实现类。
有时候由于枚举类型很多,系统需要的 typeHandler 也会很多,如果采用配置也会很麻烦。 这个时候可以考虑使用包扫描的形式,那么就需要按照以下代码配置了。
只是这样就没法指定 jdbcType 和 javaType 了。 不过我们可以使用注解来处理它们。我们把 MyTypeHandler 的声明修改一下。
枚举 typeHandler
在绝大多数情况下,typeHandler 因为枚举而使用,MyBatis 已经定义了两个类作为枚举类型的支持。
但是系统的两个枚举局限性比较大,一般自定义typeHandler来实现枚举类型转换。
Blob 字段支持
MyBatis 对数据库的 Blob 字段也进行了支持,它提供了一个 BlobTypeHandler。 数据库的 Blob 字段 , 对应 byte[]。 一次性将大量数据加载JVM,可能会有性能压力。 可以考虑文件流的形式,把 byte[] 修改为 InputStream。 系统会使用 BlobInputStreamHandler 来转换结果。
ObjectFactory 对象工厂
不常用。 当创建结果集时,MyBatis会使用一个对象工厂来完成创建这个结果集的实例。默认会使用 DefaultObjectFactory。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现,继承 DefaultObjectFactory。 自定义对象工厂,可以创建这个结果集对象实例的时候,增加一些自定义操作。
ObjectFactory 接口很简单,它包含两个创建实例用的方法,一个是处理默认无参构造方法的,另外一个是处理带参数的构造方法的。 另外,setProperties 方法可以被用来配置 ObjectFactory,在初始化你的 ObjectFactory 实例后, objectFactory 元素体中定义的属性会被传递给 setProperties 方法。
配置objectFactory
配置后,MyBatis就会采取配置的 ObjectFactory 来生成结果集对象。
插件
后面详细介绍
environments 运行环境
在 MyBatis 中,运行环境主要的作用是配置数据库信息。 可以配置多个运行环境,现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置。
尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单。 每个数据库对应一个 SqlSessionFactory 实例。
比如,mybatis配置文件,配置两个environment。
创建SqlSessionFactory
如果不指定环境,使用默认的 HO
可以看到上面,一个环境,又分为两个可配置的元素
transactionManager 事物管理器
可以把事务管理器配置成为以下两种方式
JDBC 这个配置直接使用了 JDBC 的提交和回滚,它依赖从数据源获得的连接来管理事务作用域。
MANAGED 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 J2EE 应用服务器的上下文)。 默认情况下它会关闭连接。 然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。
例如
另外,如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。
dataSource 数据源
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
有三种内建的数据源类型
UNPOOLED 采用非数据库池的管理方式,每次请求都会打开一个新的数据库连接。 对有些数据库而言,应用是否使用连接池并不重要,比如数据库侧使用了连接池,那么它也是一个比较理想的选择。
作为可选项,你也可以传递属性给数据库驱动。 只需在属性名加上 "driver." 前缀即可,例如
配置数据库连接池
Mybatis 没有帮开发者实现 ,比如 c3p0 数据库连接池,故需要使用者自己实现 c3p0 来加载数据连接池。
只要继承 UnpooledDataSourceFactory 并把 dataSource 实现。我们的 mybatis 就实现了 c3p0 数据库连接池。
mybatis-config.xml 配置文件配置 c3p0
可以使用容器配置的数据源。 容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性
比如,在Tomcat配置全局的JNDI
在MyBatis中配置
databaseIdProvider 数据库厂商标识
用来区别不同的数据库,同样的查询语句,使用不同的SQL来处理。比如
调用getRole的时候,根据数据库的不同,选择不同的SQL。
mappers 映射器
映射器是MyBatis最复杂,最核心的组件,这里只谈论如何引入映射器,映射器的详细在下面介绍。
有四种方式引入映射器。
使用相对于类路径的资源引用
使用完全限定资源定位符(URL)
使用映射器接口实现类的完全限定类名
将包内的映射器接口实现全部注册为映射器
4. MyBatis 映射
映射器是MyBatis最复杂,最核心的组件,由一个接口和XML文件(或者注解)组成。 在映射器可以配置参数,各类SQL,存储过程,缓存,级联等复杂的内容。并且可以通过简易的映射规则映射到指定的POJO。
select 元素
在 SQL 映射文件中
<select>
元素用于映射 SQL 的 select 语句。 select元素常用的属性就是,id,parameterType,resultType,resultMap,如果要设置缓存,会用到flushCache,useCache,缓存的属性放在后面介绍。一个简单查询的 select 元素
除了这个SQL,还需要提供一个对应的接口才能运行。
是被传进去的参数,告诉 MyBatis 创建一个预处理语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个 ? 来标识,并被传递到一个新的预处理语句中,就像这样
自动映射和驼峰映射
MyBatis提供了自动映射的功能,默认是开启的。 在setting元素中,有两个可配置选项 autoMappingBehavior 和 mapUnderscoreToCamelCase 。分别控制自动映射 和 驼峰映射。 一般,使用自动映射比较多,驼峰映射要求比较严格一些,降低了灵活性,实际运用不算太广。
autoMappingBehavior 的取值范围
一般,使用默认的就可以。
SQL的列名和属性一直,那么就会形成自动映射。
SQL映射
映射接口
原来的列名 role_name 被别名 roleName 替换了,这样就和POJO的属性名一致。此时,MyBatis会将这个结果映射到POJO的属性上面,完成自动映射。而无需再进行任何配置。
传递多个参数
参数名 u_name 和 u_sex 是 Map 的 key。
使用这个SQL映射。
一般不推荐这种方式。
使用map,可读性差。可以使用注解的方式
不用指定 parameterType,让 MyBatis自动探索就可以。
先定义个参数的POJO
把接口方法定义成
使用这个SQL
有时候,可以混合使用JavaBean和注解的方法
如果加上注解参数的前缀
使用 resultMap 映射结果
自动映射和驼峰映射规则简单,但是无法定义多的属性,比如typeHandler,级联等等。 为了支持复杂的映射,select 元素提供了 resultMap 属性。
先简单的介绍一下
分页参数 RowBonds
MyBatis内置了一个专门处理分页的类 RowBonds。
使用它十分简单,在接口增加一个RowBounds参数就可以了
在映射器XML中,不需任何改变,RowBounds是MyBatis的一个附加参数,MyBatis会自动识别它,用它来进行分页。
使用映射接口
insert 元素
insert元素用于映射插入语句,MyBatis 执行完一条插入语句后将返回一个整数表示其影响的行数。
主键回填
上面的insert操作,没有插入id列,因为在MySQL侧,采用了自增主键,MySQL会为它生成主键。 有时候需要,添加完数据之后,得到这个主键,继续做其他关联数据的插入。MyBatis提供了这样的支持。
JDBC的Statement对象,在执行插入SQL之后,可以通过 getGenerateKeys方法获得数据库生成的主键。 当然,这个是需要数据库驱动的支持,比如MySQL,SQLServer提供了这样的自增字段。 在insert语句中有一个开关属性,useGeneratedKeys ,用来控制是否打开这个功能。默认值是false。 当变成true时,还需要配置 keyProperty 或者 KeyColumn ,告诉系统把生成的主键放在哪个属性中。
自定义主键
使用 selectKey 元素进行自定义主键。
keyProperty 指定哪一个属性作为POJO的主键。 order=BEFORE 表示在当前SQL执行之前执行。
update 元素 和 delete 元素
update 和 delete 类似,返回整数,代表影响了数据库的记录行数。
sql 元素
sql 元素的作用在于可以定义SQL的一部分,便于后面重用,最典型的是列名。
sql 元素还支持变量传递
参数
#{id}
就是参数,参数也可以详细指定尽管上面这些选项很强大,但大多时候,你只须简单指定属性名,顶多要为可能为空的列指定 jdbcType,其他的事情交给 MyBatis 自己去推断就行了。
存储过程参数支持
MyBatis对存储过程提供了支持。提供了 IN 参数,OUT 参数 和 INOUT 参数。
另外,除了简易的输入输出参数,也可以使用游标,MyBatis也对游标提供了支持。 如果把jdbcType声明为Cursor,那么就会使用ResultSet来处理
比如
持久化参数类 UserParam public class UserParam { ...... private List
addresses; }字符串替换
默认情况下,使用
#{}
参数语法时,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。 这样做更安全,通常也是首选做法。 不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。比如,根据字段查询,有这三种接口方法。
SQL几乎一样,只是查询字段不一样。
可以只写这样一个方法
其中
${column}
会被直接替换,而#{value}
会被使用 ? 预处理。可以像下面这样来达到上面的功能
字符串替换是安全性很弱,容易被SQL注入,所以用的时候请保持小心。 原则是:能用参数替换就用参数,不能用或者动态字段查询才用字符串替换。
resultMap 元素
resultMap的作用是 定义映射规则,级联的更新,定制类型转化器 等。 resultMap 元素表示结果映射集,也就是 SQL 到 JavaBean 的映射关系定义。
MyBatis现在的只支持 resultMap 查询,不支持更新或者保存,更没有级联的更新,删除和修改。
resultMap 元素包含了一些子元素,结构如下
子元素 constructor 用于配置构造方法,当 POJO 未定义无参数的构造方法时使用。 比如一个RoleBean,没有构造方法, 构造方法是
public RoleBean(Long id, String roleName);
这样 MyBatis 就会使用对应的构造方法来构造POJO。
一条查询SQL执行后,就会返回结果,而结果可以使用map存储,也可以使用POJO存储。
使用 map 存储结果集
使用 map 原则上可以匹配所有的结果集。但是使用map,意味着可读性下降,因为需要进一步了解map的键值。 所以更推荐使用POJO方式。
使用 POJO 存储结果集
POJO是最常用的方式,一方面可以使用自动映射,需要更加复杂的映射或者级联,可以使用resultMap
SQL的列名,和 resultMap 中result 元素的 column 一致。 typeHandler,javaType,jdbcType 等更多内容
级联
级联不是必须的,级联的好处是获得关联数据十分便捷,但是级联过多会增加复杂度,所以当级联超过3层时,就不要考虑级联了。
MyBatis中的级联分为三种
MyBatis 没有多对多的级联,而且可以通过两个一对多级联进行替换,所以 MyBatis 不支持多对多级联。
一对一映射
在 MyBatis 中,通过 resultMap 元素的子元素
<association>
处理这种一对一级联关系。 比如一个 Person类 和 一个 Idcard类 ,是一对一的关系。(人和身份证)任意一方引入对方的主键作为外键。 比如,在person表中,增加外键 idcard_id,并且在Person类,增加 Idcard idcard 属性。
Person类
Idcard类
创建两张表对应的映射文件 IdCardMapper.xml 和 PersonMapper.xml。
IdCardMapper.xml
PersonMapper.xml PersonMapper.xml 文件中以 3 种方式实现 根据 id 查询个人信息 的功能
第三种方法,需要再创建一个POJO
然后创建映射器接口
最后使用映射器取得结果
总结一下,一对一查询,主要有两种方式。
嵌套查询是先查一个表 , 根据这个表里面的结果的外键id, 去再另外一个表里面查询数据 ,执行两个SQL。 通过 association 配置, 另外一个表的查询通过 select 属性配置。
联合查询是几个表联合查询, 只查询一次。 通过在 resultMap 里面配置 association 节点配置一对一的类就可以完成;
一对多映射
用户 User 和 订单 Order 之间的关系为例,讲解一对多级联查询 实现 根据 uid 查询用户及其关联的订单信息 的功能。
数据库中,多的一方,orders表中,增加user表的外键。
user表,只包含自己的id,username,address 就可以。
创建持久化类 Orders.java
User.java
创建映射XML
OrdersMapper.xml
UserMapper.xml 用三种方法来做一对多的关联
第三种方式,需要创建一个接收结果的POJO
剩下的和一对一没有什么区别。
可以看出来,一对多,也是有 嵌套查询(2次SQL)和 关联查询(1次SQL) 这两种主要方式。
鉴别器 discriminator
mybatis 可以使用 discriminator 判断某列的值,然后根据某列的值改变封装行为。 它很像 Java 语言中的 switch 语句。
延迟加载
可以一次性把常用的级联数据通过SQL直接查询出来,不常用的级联数据可以采取延迟加载的策略。
MyBatis的 settings 配置中存在两个元素可以配置级联。
lazyLoadingEnabled 延迟加载的全局开关,默认是false。 开启时,所有的关联对象都会延迟加载。在特定的关联关系中,可以设置fetchType属性来覆盖该项的开关状态。
aggressiveLazyLoading
控制是否采用层级加载。一般都使用默认的false。
延迟加载的全局配置参数,一般这么配置
另外,在MyBatis中使用fetchType属性,可以个别设定关联关系的加载策略。 fetchType 实行可以出现在
<association>
和<collection>
。存在两个设定值。fetchType属性会忽略全局配置项 lazyLoadingEnabled 和 aggressiveLazyLoading。
多对多级联
MyBatis 没有实现多对多级联,这是因为多对多级联可以通过两个一对多级联进行替换。 例如,一个订单可以有多种商品,一种商品可以对应多个订单,订单与商品就是多对多的级联关系。 使用一个中间表(订单记录表)就可以将多对多级联转换成两个一对多的关系。
创建数据库表
创建持久化类 注意,中间表是不需要创建关联的持久化类的 商品类
订单类
在映射XML中,配置 collection 元素即可。 OrderMapper.xml
ProductMapper.xml
这样是,根据 order 的 id,找到他关联的 product 列表。 同样的道理,也可以设置,通过 product 的 id,找到关联的 order 列表。
缓存
MyBatis 的 查询缓存 分为 一级缓存 和 二级缓存。 二级缓存是在 SqlSessionFactory 上的缓存,一级缓存是在SqlSession上的缓存。 默认情况下,MyBatis系统会开启一级缓存。
一级缓存默认开启。 一级缓存是范围是同一个 SqlSession 对象。 当调用 SqlSession 的修改,添加,删除,commit(),close() 等方法时候,就会清空一级缓存。
MyBatis 自带二级缓存。也可以使用第三方提供的缓存。
二级缓存的范围是,同一个 namespace 生成的 mapper 对象,就可以共享缓存。
比如
只要产生的 xxxMapper 对象,来自于同一个namespace,则这些对象共享二级缓存。
MyBatis默认二级缓存关闭,开启二级缓存。
另外 ,什么时候会把数据存放到二级缓存呢? 也就是触发二级缓存的时机。
一级缓存存放的对象升级到二级缓存,是在sqlSession的 commit(), close() 调用的时候。 也就是当一级缓存清空的时候,如果符合条件,就会把对象存放在二级缓存中。 因为存放二级缓存对象,实质上是将对象序列化到硬盘,也是比较损耗性能的,所以,不能随时把对象写进硬盘, 而是在一级缓存清空的时候,一次性将数据写进二级缓存。
禁用二级缓存
在映射器中,加上
<cache />
,将所有的select都启用二级缓存,如果想让某一个select禁用二级缓存的话。 加上 userCache=false 就可以了。清理二级缓存
和一级缓存一样,SqlSession.commit(),close() 等方法时候清理。
一般执行SqlSession.commit()后即被清空,但是,SqlSession.commit()中的SqlSession不可以是查询语句的! 在某个查询语句中,使用commit无法清理二级缓存。 在增删改后,SqlSession.commit()会清理二级缓存。
flushCache 刷新缓存
设置statement配置中的
flushCache="true"
属性 默认情况下为true 即 刷新缓存,如果改成 false 则不会刷新。 使用缓存时如果手动修改数据库表中的查询数据会出现脏读。flushCache默认为false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存。 useCache默认为true,表示会将本条语句的结果进行二级缓存。
flushCache默认为true,表示任何时候语句被调用,都会导致本地缓存和二级缓存被清空。 useCache属性在该情况下没有。
使用第三方 二级缓存 ehcache
整合 Ehcache
导入需要的jar包 Ehcache-core.jar mybatis-Ehcache.jar
编写 Ehcache 的配置文件
名字是 Ehcache.xml
变成
动态 SQL
MyBatis 提供对 SQL 语句的动态组装能力。从而不需要大量 Java 代码进行判断,然后再SQL。 大量的判断都可以在MyBatis的映射XML里配置。
if 元素
相当于 Java 的 if 语句。常常与test属性联合使用。
choose when otherwise 元素
类似于 switch case default。
where 元素
可以看到,前面 加上了
where 1=1
,如果不加上 1=1 ,这个SQL 语句语法是不正确的。 比如可以用where 元素来处理SQL来达到预期的效果。 当where元素里面的条件成立,才会加入 where 这个SQL关键字组装的SQL里面,否则不加入。 MyBatis 将智能处理。如果所有的条件都不满足,那么 MyBatis 就会查出所有的记录,如果输出后是以 and 开头的,MyBatis 会把第一个 and 忽略。
trim 元素
trim 元素可以去掉一些特殊的SQL语句。 下面的形式,就可以替换 where 元素。
trim元素意味着要去掉一些特殊字符串,prefix代表的语句的前缀,prefixOverrides 代表的是需要去掉哪种字符串。
set 元素
Hibernate做更新的时候,都是全部字段发给SQL,不需要更新的字段也发给了SQL。这样显示比较冗余。 在 MyBatis 中,常常可以使用 set 元素来避免这样的问题。
foreach 元素
foreach 元素 作用是遍历集合。 能很好的支持 数据,List 和 Set 接口的集合。对此提供遍历功能。 通常用于 SQL 中的 in 关键字。
collection 配置的userList是传递进来的参数名称。 item 是集合里的元素。 index 是集合元素的下表。
bind 元素
在进行模糊查询的时候,如果是MySQL,用一个concat函数,用%和参数相连。 如果是Oralce,使用连接符号 || 来连接,这样SQL就需要提供两种方式去实现。 但是有了bind元素,就不必使用数据库语言,而是使用MyBatis的动态SQL即可完成。
这样就绑定了新的变量 paran_uname ,可以在SQL的其他地方使用。
MyBatis 事物
增,删,改 数据,需要 sqlSession.commit()
也可以设置自动提交。 自动提交,每个DML(增,删,改)操作,自动提交,无需 sqlSession.commit() 设置自动提交,只需要在创建SqlSession的时候,传一个 true 作为参数。
总结一下
以前使用JDBC的时候,如果要开启事务,我们需要调用conn.setAutoCommit(false)方法来关闭自动提交,之后才能进行事务操作,否则每一次对数据库的操作都会持久化到磁盘中。
而mybatis呢,如果底层使用JDBC(在mybatis.xml中配置的transactionManager标签的type设为jdbc的话),那么,mybatis会默认开启事务,也就是说,mybatis默认是关闭自动提交的。
默认情况下,在mybatis中,如果我们执行了数据库的修改操作(insert、update、delete),必须调用session.commit()方法,所做的修改才能持久化到磁盘。
另外,如果要使用事物,需要底层数据库支持事物,比如MySQL的 Inndb 引擎。
MyBatis 日志
整合 Log4j
导入 jar包
在配置文件中,开启日志,并使用Log4j
如果不指定,MyBatis就会根据一下顺序寻找日志
日志级别
如果设置为info,则只显示info及以上级别的信息 建议:在开发时设置debug,在运行时设置为info或以上
MyBatis 的解析和运行原理
主要分为两个运行过程
构建 SqlSessionFactory 过程
SqlSessionFactory 是 MyBatis 的核心之一,最重要的功能就是提供创建 MyBatis 的核心接口 SqlSession。 要创建 SqlSessionFactory,要提供配置文件和相关的参数。MyBatis 采用 Builder 模式创建SessionFactory。
构建分2步
解析配置XML,读出所配置的参数。读取的内容存入 Configuration 对象中。 Configuration采用的单例模式,几乎所有的 MyBatis 的配置,都会存放在这个单例对象中。
使用 Configuration 接口去创建SqlSessionFactory。 SqlSessionFactory 是一个接口,不是一个实现类。
构建 Configuration
Configuration 的作用是
构建 SqlSessionFactory
有了 Configuration 对象,就可以构建 SqlSessionFactory 了
SqlSessionFactoryBuilder内部,通过 XMLConfigBuilder 来读取 Mybatis 配置。
总结一下
SqlSession 运行过程
SqlSession 下的四大对象
Executor 执行器 它调度 StatementHandler ,ParameterHandler,ResultSetHandler 等来执行对应的SQL。
StatementHandler 数据库会话器 使用数据库的Statement 或者 PreparedStatement 执行操作。很多重要的插件,都是通过拦截它来实现的。
ParameterHandler 参数处理器 用来处理SQL参数。
ResultSetHandler 结果处理器 进行数据集 ResultSet 的封装。
SqlSession是通过执行器 Executor 调度 StatementHandler 来运行的。 而 StatementHandler 经过3步
parameterize 是调用 ParameterHandler 的方法设置的,而参数是根据类型处理器 typeHandler 处理的。 query/update 方法通过 ResultSetHandler 进行处理结果的封装,如果是update,返回整数,如果是 query,通过类型处理器 typeHandler 处理结果类型。然后用 ObjectFactory 提供的规则来组装对象,返回给调用者。
插件
Mybatis插件又称拦截器。
Mybatis采用责任链模式,通过动态代理组织多个插件(拦截器),通过这些插件可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件。
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。 默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
总体概括为
Mybatis 四大接口
Mybatis插件能够对这四大对象进行拦截,可以包含到了Mybatis一次查询的所有操作。可见Mybatis的的插件很强大。
Mybatis的插件实现要实现 Interceptor 接口。
要想一针见血地理解MyBatis插件机制,只需要明白一点:原身和变身。 也就是说,一旦配置上插件,ParameterHandler,ResultSetHandler,StatementHandler,Executor这四大核心对象,将会生成变身,是一种代理对象,而不再是原身。
在Java里面,我们想拦截某个对象,只需要把这个对象包装一下,用代码行话来说,就是重新生成一个代理对象。
一旦我们在mybatis-config.xml里面配置了插件
以ParameterHandler为例
第一步:根据插件配置,利用反射技术,创建interceptor拦截器
第二步:创建原身
第三步:原身+拦截器---->变身
在这一步,将parameterHandler和interceptor包装到一起,生成了变身,并重新赋值给parameterHandler变量。
没有插件的运行过程
有插件的运行过程
MyBatis 批量处理
加入有插入20万数据的批处理需要。 MyBatis有几种方式。
Mybatis内置的 ExecutorType有三种,默认是Simple,该模式下它为每个语句的执行创建一个新的预处理语句,单条提交sql。 而BATCH模式 ,重复使用已经预处理的语句,只预编译一次,然后每次insert,只是插入参数,显示性能更好,更适合做批处理模式。
使用 BATCH 模式非常简单,只需要在openSession的时候,添加参数。
上面是推荐的批处理的处理方式。
另外,也可以有别的方式 比如,在Mapper.xml当中使用 foreach循环的方式进行insert
PersonDao.java
PersonDao.xml
PageHelper 分页
Mybatis 提供了一个简单的逻辑分页使用类RowBounds。
逻辑分页 会将所有的结果都查询到,然后根据RowBounds中提供的offset和limit值来获取最后的结果。
物理分页 就是我们在sql语句中指定limit和offset值。不同的数据库,使用的分页SQL也是不一样的
PageHelper 分页
pagehelper分页插件因此而诞生,他的原理是利用mybatis拦截器,在查询数据库的时候,拦截下SQL,然后进行修改。从而实现物理分页。
添加maven依赖
在 mybatis-config.xml 中定义
使用 PageHelper 插件也非常简单。
上面这样,就可以物理分页了。
还可以使用PageInfo类,PageInfo包含了非常全面的分页属性。