Mybatis快速入门【解决开发中常遇到的坑】
一、MyBatis开发流程
1.1 添加MyBatis依赖jar
<dependencies>
<!-- 添加Mybatis3.4.6相关依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- 添加数据库依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.42</version>
</dependency>
</dependencies>
1.2 开发一个实体映射类
@Getter
@Setter
public class Blog {
private int id;
private String blogName;
}
1.3 开发一个SQL映射文件
在src/main/resource下创建与当前表对应的SQL映射文件用于声明SQL语句
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
1.4 开发MyBatis核心配置文件
在src/main/resources下创建MyBatis-config.xml作为核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
对于基础不好的同学,请查看《在XML配置文件中使用properties文件的键值作为变量化参数》,没有使用spring框架的话,则将上述的${xxx}替换为具体的值固定写死即可。
1.5 开发MyBatis基本调用流程
public static void main(String[] args) throws IOException {
Blog blog = new Blog();
blog.setId(1);
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = factory.openSession();
sqlSession.insert("selectBlog",blog);
sqlSession.commit();
sqlSession.close();
}
二、MyBatis工作原理与工作流程
如想深入理解MyBatis欢迎查看我另外一篇文章《深入理解MyBatis框架》
三、插入操作主键值获取
3.1 当前表支持主键自动增长
在JDBC技术中,可以通过Statement接口中getGeneratedKeys()方法获得本次插入后得到自动增长主键值.MyBatis框架也采用这个技术.因此MyBatis在插入完毕后也可以获得本次插入数据id.做法如下:
<!--
useGeneratedKeys="true"
表示MyBatis可以通过getGeneratedKeys方法获得本次自动增长的值
keyProperty="id"
表示Mybatis将获得自动增长值赋值给当前实体类对象的哪个属性
如:
Blog blog = new Blog();
session.insert("insertBlog",blog);
那么,在插入完毕后,blog.id将会出现本次插入的id值
-->
<insert id = "insertBlog" useGeneratedKeys="true" keyProperty="id">
INSERT INTO blog(id,name) VALUES(#{id},#{name})
</insert>
3.2 当前表不支持主键自动增长
在Mysql数据库中,可以通过max函数获得当前表中最后一条插入数据id.
在MyBatis中,也可以通过这种方式来获得主键值
<!--
selectKey标签:
用于声明查询当前表主键值的SQL语句
keyProperty="id"
表示将查询结果赋值给当前实体类(Blog)对象中id属性
resultType="Integer"
表示将查询结果转化为Integer类型
order="BEFORE"
表示当前查询语句要在insert语句之前执行
-->
<insert id = "insertBlog">
<selectKey resultType="Integer" keyProperty="id" order="BEFORE">
SELECT MAX(id)+1 from blog
</selectKey>
INSERT INTO blog(id,name) VALUES(#{id},#{name})
</insert>
四、查询操作
4.1 将查询结果封装为Map集合或则List集合
在SqlSession接口中,可以分别使用selectList方法和selectMap方法将查询结果分别封装为List集合和Map集合
4.2 查询单个记录
在SqlSession接口中,可以使用selectOne方法获得一个数据行并将数据行封装为一个实体类对象
4.3 模糊查询
<!-- 模糊查询 -->
<select id="blogQuery" resultType="cn.itcats.Bolg">
<!-- select * from blog where name like '%' #{name} '%' -->
<!-- select * from blog where name like concat('%',#{name},'%') -->
<!-- select * from blog where name like '%${name}%' -->
</select>
//在Java代码中添加sql通配符,能防止sql注入
String wildcardname = “%smi%”;
list<name> names = mapper.selectlike(wildcardname);
<select id=”selectlike”>
select * from foo where bar like #{value}
</select>
五、typeAliases使用别名
在mapper.xml中,定义很多的statement,而statement需要parameterType指定输入参数的类型、需要resultType指定输出结果的映射类型。如果在指定类型时输入类型全路径,不方便进行开发,可以针对parameterType或resultType指定的类型定义一些别名,在mapper.xml中通过别名定义,方便开发。typeAliases是MyBaits框架中提供别名转换器,可以对使用实体类名称设置一个简短的别名,从而简化开发负担.使用方式如下:
方式一:为每一个实体类都设置一个别名
<!-- 为每一个实体类设置一个具体别名 -->
<typeAliases>
<typeAlias type="cn.itcats.Blog" alias="Blog"/>
</typeAliases>
此时,在SQL映射文件就可以使用这个”Blog”别名了
<!-- 查询结果返回List或者Map -->
<select id=”blogFind” resultType="Blog">
select * from blog
</select>
<!-- 查询单个记录 -->
<select id = "blogFindById" resultType="Blog" >
select * from blog where id = #{id}
</select>
方式二:为某个包下所有的类设置默认别名,此时别名就是当前类的简单名称
<typeAliases>
<!-- 为cn.itcats.beans包下的每一个类都设置默认别名,别名为当前类的简单名称 -->
<package name="cn.itcats.beans" />
</typeAliases>
六、environments 环境
MyBatis 可以配置多种环境。这会帮助你将 SQL 映射应用于多种数据库之中。但是要记得一个很重要的问题:你可以配置多种环境,但每个数据库对应一个 SqlSessionFactory。所以,如果你想连接两个数据库,你需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,你就需要三个实例,以此类推。
为了明确创建哪种环境,你可以将它作为可选的参数传递给 SqlSessionFactoryBuilder,可以接受环境配置的两个方法签名是:
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment);
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader,environment,properties);
如果环境被忽略,那么默认环境将会被加载,按照如下方式进行:
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader);
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader,properties);
如下图所示,我们配置了两个开发环境,[development1]和[development2].默认的开发环境是[development1].
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 环境1: -->
<environments default="development1">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
<environments default="development2">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url2}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
在开发时,如果需要使用[development2],此时可以通过如下代码来制定:
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment);
具体实例:
@Before
public void start() throws IOException{
InputStream is = Resources.getResourceAsStream("myBbatis-config.xml");
//此时将读取开发环境2
SqlsessionFactory factory = new SqlSessionFactoryBuilder().build(is,"development2");
//...
}
七、mappers 映射器
7.1 用法
通俗来说就是:这些配置会告诉了 MyBatis 去哪里找映射文件 ,用法如下:
第一种(常用):
resource指向的是相对于类路径下的目录
第二种 :
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
第三种:【容易掉进的坑】
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="cn.itcats.mapper.AuthorMapper"/>
<mapper class="cn.itcats.mapper.BlogMapper"/>
<mapper class="cn.itcats.mapper.PostMapper"/>
</mappers>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
具体操作步骤:
第一步:在[src/main/java]创建接口[cn.itcats.mapper.AuthorMapper]
第二步:在[src/main/resources]下的[cn.itcats.mapper]文件夹下创建[AuthorMapper.xml]的SQL映射文件
这两个文件在MAVEN工程位置如下
第四种(推荐): 【容易掉进的坑】
注册指定包下的所有mapper接口:
此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="cn.itcats.mapper"/>
</mappers>
7.2 MyBatis中接口和对应mapper文件配置深入解析【坑多】
首先要说明的问题是,Mybatis中接口和对应的mapper文件不一定要放在同一个包下,放在一起的目的是为了Mybatis进行自动扫描,并且要注意此时java**接口的名称和mapper文件的名称要相同**,否则会报异常,由于此时Mybatis会自动解析对应的接口和相应的配置文件,所以就不需要配置mapper文件的位置了。
7.2.1 默认MAVEN构建
如果在工程中使用了maven构建工具,那么就会出现一个问题:我们知道在典型的maven工程中,目录结构有:src/main/java和src/main/resources,前者是用来存放java源代码的,后者则是存放一些资源文件,比如配置文件等,在默认的情况下maven打包的时候,对于src/main/java目录只打包源代码,而不会打包其他文件。所以此时如果把对应的mapper文件放到src/main/java目录下时,不会打包到最终的jar文件夹中,也不会输出到target文件夹中,由于在进行单元测试的时候执行的是/target目录下/test-classes下的代码,所以在测试的时候也不会成功。
此时我们在target中并不会发现有AuthorMapper.xml文件存在
此时运行程序会抛出如下异常BindingException:Invalid bound statement(not found)
为了实现在**maven默认环境下打包时,Mybatis的接口和mapper文件在同一包中,可以通过将接口文件放在src/main/java某个包中,而在src/main/resources**目录中建立同样的包,这是一种约定优于配置的方式,这样在maven打包的时候就会将src/main/java和src/main/resources相同包下的文件合并到同一包中。
此时在target下,我们是可以看到AuthorMapper.xml文件的,此时程序可以正常运行。
7.2.2 更改MAVEN配置
如果不想将接口和mapper文件分别放到src/main/java和src/main/resources中,而是全部放到src/main/java,那么在构建的时候需要指定maven打包需要包括xml文件,具体配置如下:
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
八、typeHandlers 类型转换器
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集(ResultSet)中取出一个值时, 都会用类型处理器typeHandlers将获取的值以合适的方式转换成Java 类型。下表描述了一些默认的类型处理器。
你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法参照:
http://www.mybatis.org/mybatis-3/zh/configuration.html#typeHandlers
九、MyBatis框架Mapper配置文件详解
9.1 Mapper配置文件标签介绍
- insert – 映射插入语句
- update – 映射更新语句
- delete – 映射删除语句
- select – 映射查询语句
- sql – 可被其他语句引用的可重用语句块
- resultMap-确定实体类属性与表中字段对应关系
9.2 **mapper中namespace**
<mapper>标签是SQL映射文件中根目录标签.在这个标签中只有输一个属性
<mapper namesapce="">
9.2.1 namespace属性有什么作用呢?
在MyBatis中,Mapper中的namespace用于绑定dao/mapper接口的,即面向接口编程。它的好处在于当使用了namespace之后就可以不用写接口实现类,业务逻辑会直接通过这个绑定寻找到相对应的SQL语句进行对应的数据处理。
9.2.2 namespace属性赋值规则
接口全限定名【最常用】,如cn.itcats.mapper.UserMapper
还有一种为短名称(比如“selectAllThings”)如果全局唯一也可以作为一个单独的引用。如果不唯一,有两个或两个以上的相同名称(比如“com.foo.selectAllThings ”和“com.bar.selectAllThings”),那么使用时就会收到错误报告说短名称是不唯一的,这种情况下就必须使用完全限定名。
9.3 parameterType属性
在
(2)resultType属性可以指定一个基本类型也可以是一个实体类类型
(3)使用resultType属性为实体类类型时,只有查询出来的列名和实体类中的属性名一致,才可以映射成功. 如果查询出来的列名和pojo中的属性名全部不一致,就不会创建实体类对象。但是只要查询出来的列名和实体类中的属性有一个一致,就会创建实体类对象
(4)resultType属性无法与resultMap属性同时出现.
9.7 <resultMap>标签
MyBatis框架中是根据表中字段名到实体类定位同名属性的.如果出现了实体类属性名与表中字段名不一致的情况,则无法自动进行对应.此时可以使用resultMap来重新建立实体类与字段名之间对应关系,当然也可以使用sql语句中使用别名保持映射关系。
方式一:
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
引用它的语句使用 resultMap 属性就行了(注意我们去掉了 resultType 属性)。比如:
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
方式二:
<select id="selectUsers" resultType="com.someapp.model.User">
select user_id as id, user_name as username, hashed_password as password
from some_table
where id = #{id}
</select>
9.8
首先,我们如下两条SQL映射
<select id="selectUsersById" resultMap="userResultMap">
select user_id, user_name, hashed_password
from user
where user_id = #{id}
</select>
<select id="selectUsersByUsername" resultMap="userResultMap">
select user_id, user_name, hashed_password
from user
where user_name = #{username}
</select>
这两条查询映射中要查询的表以及查询的字段是完全一致的.因此可以
<sql id="UserFindSql">
select user_id, user_name, hashed_password
from user
</sql>
在需要使用到这个查询的地方,通过
<select id="selectUsersById" resultMap="userResultMap">
<include refid="UserFindSql"></include>
where user_id = #{id}
</select>
<select id="selectUsersByUsername" resultMap="userResultMap">
<include refid="UserFindSql"></include>
where user_name = #{username}
</select>
十、MyBatis接口编程
10.1 传统的MyBatis开发流程问题
在讲Mybatis接口式编程之前,我们先回忆一下前面是如何调用映射文件中的SQL代码的。通常情况下,都是使用SqlSession实例的selectXXX(selectOne, selectList, selectMap)方法来执行映射文件中相应的SQL语句的,这些方法都有一个共同的特征,那就是第一个参数都是String类型的,我们需要使用这个参数明确告之Mybatis我们是需要执行映射文件的哪一个元素下的SQL语句,所以这个参数内容应该是映射文件的名称空间加上相应元素的id值,如:
@Test
public void Test01(){
//namespace + id
List list = session.selectList("cn.itcats.User.UserFind");
System.out.println(list.size());
}
这里存在一些潜在的问题:
- 为了确保名称空间的唯一性,通常会使用相对较长的、且有一定含义的字符串来作为其值,这样就很难保证我们在代码不出现拼写错误的情况,即使是直接从映射文件拷贝过来的,也存在不经意间被修改的可能性;
- 从selectXXX方法的签名可以看到,她的第二个参数是Object类型,那么如果我们传入的参数类型与映射文件中由parameterType属性指定的类型不一致时,将会出现不可预知的错误
- 同样,selectXXX方法返回值使用了泛型,我们须确保用于接收其返回值的变量类型与映射文件中属性resultType指定的类型相一致
Mybatis为了规避上述风险,提供了接口编程
10.2 什么是接口编程?
接口式编程,我们可以简单的理解为Mybatis为映射文件定义了一个代理接口,以后全部通过这个接口来和映射文件交互,而不再是使用以前方法。
10.3 接口编程实现
(1)声明一个接口
public interface UserMapper{
public User findUserById(Integer id);
}
(2)修改Mapper文件命名空间
命名空间应该是接口完整名称
<mapper namespace="cn.itcats.mapper.UserMapper">
<!-- id为interface方法的名称 -->
<select id = "findUserById" resultMap="userResultMap">
select * from user where user_id = #{id}
</select>
</mapper>
(3)测试与调用
@Test
public void Test01(){
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.findUserById(1);
System.out.println(user.getUsername());
}
十一、Mybatis动态sql
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
参照:http://www.mybatis.org/mybatis-3/zh/dynamic-sql.html
还没有评论,来说两句吧...