Mybatis使用入门,这一篇就够了
- 2019 年 10 月 3 日
- 筆記
mybatis中,封装了一个sqlsession 对象(里面封装有connection对象),由此对象来对数据库进行CRUD操作。
运行流程
mybatis有一个配置的xml,用于配置数据源、映射Mapping,xml的文件名可以任取,为了方便,我们还是起mybatis-config.xml
我们读取此配置的xml,获得一个sqlsession,之后由此对象类进行数据库的CRUD操作
Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader); SqlSession sqlSession = factory.openSession();
入门使用
1. 创建实体类和Dao类
2. 配置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> <!-- 引入外部资源文件--> <properties resource="jdbc.properties"/> <!-- 配置数据源环境 --> <environments default="development"> <environment id="development"> <!-- 数据库事务管理类型 --> <transactionManager type="JDBC"/> <!-- 数据源,type=pooled 说明是使用连接池方式,可以节省资源 --> <dataSource type="POOLED"> <!-- 调用资源文件里的用户信息--> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> </configuration>
3. 定义连接数据库工具,可以获得sqlsession对象
Dao类中每次进行CRUD操作,都要执行一次openSession方法
来获得SqlSession对象
,造成资源的浪费和代码的重复
所以,和之前的JdbcUtil
工具类一样,我们也定义定义一个工具类MyBatisUtil
,用来返回SQLSession对象
static SqlSessionFactory sqlSessionFactory = null; static { try { // 加载mybatis配置文件,并创建SqlSessionFactory实例 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); //这个build方法可以接受几种不同的参数,如Reader/InputSteam等 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { } } public static SqlSession getSqlSession() { return sqlSessionFactory.openSession(); } public static void closeSqlSession(SqlSession sqlSession){ if (sqlSession != null) { sqlSession.close(); } }
4. sql语句写在mapper中
mapper文件放在了resources下面
Mybatis中,sql语句则是写在了xml文件中,这些xml文件也称为mapper映射文件
mapper标签如果带有xmln属性,IDEA会报解析xml错误,得把xmln属性删除
<?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"> <!-- namespace: 命名空间,用于标识每一个Mapper XML文件中的语句,预防在不同的Mapper XML文件中存在相同的语句ID --> <mapper namespace="employeeMapper"> <!-- resultType: 也称为自动映射,只有在表的列名与POJO类的属性完全一致时使用,会比较方便,全类名 --> <select id="selectAll" resultType="com.wan.bean.Employee"> select * from employee </select> </mapper>
5. 在mybatis-config.xml文件中注册mapper
<?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> <!-- 省略数据源配置--> <mappers> <mapper resource="com/wan/mapping/employeeMapper.xml"/> <!--如果还有mapper,则继续添加 --> </mappers> </configuration>
6. dao类通过sqlsession进行查询
SqlSession sqlSession = MybatisUtil.getSqlSession(); // 调用语句,如果有参数,传入参数 //参数为命名空间namespace+id,执行xml中的sql语句 List<Employee> list = sqlSession.selectList("employeeMapper.selectAll");
PS:如果是插入、更新和删除操作,还需要提交操作,默认是不会自动提交的
sqlSession.commit();
补充
1.typeAliases标签
<select id="selectAll" resultType="com.wan.bean.Employee"> select * from employee </select>
resultType
属性需要全类名,我们可以使用typeAliases
标签来简化输入
typeAliases
标签需要在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> <properties resource="jdbc.properties"/> <!--指定一个bean包 --> <typeAliases> <package name="com.wan.bean"/> </typeAliases> <!--省略配置数据源等--> </configuration>
之后我们的mapper文件中就可以这样写
<!--resultType就可以不用写全包名 --> <select id="selectAll" resultType="Employee"> select * from employee </select>
我这里只介绍用法,详解请看下面的参考链接
2.引入mapper的四种方法
1. 文件路径注册
<mappers> <mapper resource="com/wan/mapper/EmployeeMapper.xml" /> </mappers>
2. 包名扫描注册
<mappers> <package name="com.wan.mapper" /> </mappers>
使用这种,必须保证xxxMapper.java和xxxMapper.xml两者名字一模一样!而且是要在同一包里
3. 类名注册
<mappers> <mapper class="com.shizongger.chapter2.mapper.UserMapper" /> </mappers>
4. url注册
<mappers> <mapper url="file:/home/shizongger/workspace/Chapter3/src/com/shizongger/chapter2/mapper/RoleMapper.xml" /> </mappers>
SQLSession方法说明
方法名 | 说明 |
---|---|
insert | 插入 |
delete | 删除 |
update | 更新 |
selectOne | 查找单行结果,返回一个Object |
selectList | 查找多行结果,返回一个List |
使用和之前一样,第一个参数传入一个namespce+id,就可以找到指定的mapper文件里面的sql语句,并执行
CRUD
查询
Employee中,属性名和表的列名对应
<select id="selectAll" resultType="Employee"> select * from employee </select>
如果属性和表的列名不一致,可以使用列名映射resultMap标签,(也就是自动转为别名)
<!--type也是需要全包名,由于之前定义的别名,所以就可以不写--> <resultMap id="baseResultMap" type="Employee"> <!--使用映射,把对应的列名映射为对应的属性 --> <id property="empno" column="EMPNO" /> <result property="ename" column="ENAME"/> <result property="job" column="JOB"/> <result property="mgr" column="MGR"/> <result property="hiredate" column="HIREDATE"/> <result property="sal" column="SAL"/> <result property="comm" column="COMM"/> <result property="deptno" column="DEPTNO"/> </resultMap> <!--引用上面定义的resultMap--> <select id="selectAll" resultMap="baseResultMap"> select * from employee </select>
带条件查询
<select id="selectById" parameterType="int" resultMap="baseResultMap"> <!-- 如果参数类型是简单的基本或者包装类型,#{} 里面的可以任取,都是可以获得参数 --> select * from EMPLOYEE where EMPNO=#{id} </select> //使用 Employee e = sqlsession.selectOne("employeeMapper.selectById",7369);
上面的select语句相当于一个预编译语句
String s = "SELECT * FROM employee WHERE empno=?"; PreparedStatement ps = conn.prepareStatement(s); ps.setInt(1,empno);
多条件查询
可以使用where标签,当然,之前的单条件也可以使用where标签,where标签好处是会自动删除多余的and
<select id="selectSelective" parameterType="Employee" resultMap="baseResultMap"> select * from EMPLOYEE <where> <!--自动删除多余的and --> <!--#相当于从传入的bean对象(Employee)中通过getDeptno方法获得属性值 --> and deptno=#{deptno} and sal>=2000 </where> </select>
大小比较条件
条件中有大小比较,<
号得通过CDATA存放条件
<select id="selectSelective" parameterType="Employee" resultMap="baseResultMap"> select * from EMPLOYEE <where> <!--loSal为Employee的一个属性,#{loSal}相当于是通过Employee对象的get方法来获得loSal的属性值 --> and SAL>=#{loSal} <!--CDATA中的数据不会被解析器解析 --> <![CDATA[ and SAL<=#{hiSal} ]]> </where> </select>
#与$区别:
${}
用在我们能够确定值的地方,也就是我们程序员自己赋值的地方。
而#{}
一般用在用户输入值的地方!!
模糊查询:
模糊查询中需要使用%
等通配符,我们可以在xml中定义好,自动拼接通配符
<select id="selectSelective" parameterType="Employee" resultMap="baseResultMap"> select * from EMPLOYEE <where> <if test="ename != null"> <!--使用bind标签,设置格式,自动拼接通配符 --> <bind name="pattern" value="'%' + ename + '%'"/> and ENAME like #{pattern} </if> </where> </select>
动态查询
Mybatis中提供了if
标签用来实现动态查询,和JSTL标签库使用类似
<select id="selectSelective" parameterType="Employee" resultMap="baseResultMap"> select * from EMPLOYEE <where> <!--#{ename}其实是通过Employee类中的get方法来获得对象的ename属性值 --> <if test="ename != null"> and ename=#{ename} </if> <if test="job != null and job.trim().length>0"> and JOB=#{job} </if> <if test="deptno != null"> and DEPTNO=#{deptno} </if> </where> </select>
插入
主键为序列
某个主键是由oracle中的序列生成的
<insert id="insert_1" parameterType="Employee"> <!-- keyProperty: 表示将从序列获得的值赋予实体的哪个属性 order: 表示主键值生成的方式,可取值:BEFORE | AFTER 由于不同的数据库对插入的数据时主键生成方式是不同,例如: mysql and ms server: 主键生成方式为后生成方式。 oracle: 主键生成方式预生成. --> <!--调用数据库中的序列,并赋值给传入的Employee对象的empno属性 --> <selectKey keyProperty="empno" resultType="integer" order="BEFORE"> select EMP_SEQ.nextval from dual </selectKey> <!-- 如果使用这种整表插入的方式,那当数据库表的某些列可以为空值时,我将要告诉底层的JDBC驱动如何处理空值的情况,这不是mybatis所需要的, 而是底层有些JDBC驱动所需的特性,实际上就是让JDBC驱动去调用PrepareStatement.setNull()来设置空值 --> <!--如果是常用的数据类型int,date等,jdbcType可以省略不写 --> insert into EMPLOYEE values (#{empno},#{ename},#{job},#{mgr,jdbcType=INTEGER},#{hiredate,jdbcType=DATE},#{sal,jdbcType=DOUBLE},#{comm,jdbcType=DOUBLE},#{deptno,jdbcType=INTEGER}) </insert>
复用sql语句
把insert要插入的列名和数值写在sql标签里,之后方便重用,之后重用的时候需要使用include
子标签拼接sql语句
<!--insert into employee(ENAME,JOB..) values(xx,xx) --> <!--(ENAME,JOB..) --> <sql id="insert_set_column"> <!-- suffixOverrides属性,会自动把多余的“,”删除 --> <trim prefix="(" suffix=")" suffixOverrides=","> empno, <if test="ename != null">ENAME,</if> <if test="job != null">JOB,</if> <if test="mgr != null">MGR,</if> <if test="hiredate != null">HIREDATE,</if> <if test="sal != null">SAL,</if> <if test="comm != null">COMM,</if> <if test="deptno != null">DEPTNO,</if> </trim> </sql> <!--(xx,xx,xx) --> <sql id="insert_values"> <trim prefix="values(" suffix=")" suffixOverrides=","> #{empno}, <if test="ename != null">#{ename},</if> <if test="job != null">#{job},</if> <if test="mgr != null">#{mgr},</if> <if test="hiredate != null">#{hiredate},</if> <if test="sal != null">#{sal},</if> <if test="comm != null">#{comm},</if> <if test="deptno != null">#{deptno},</if> </trim> </sql> <insert id="insert_2" parameterType="Employee"> <selectKey keyProperty="empno" resultType="integer" order="BEFORE"> select EMP_SEQ.nextval from dual </selectKey> insert into EMPLOYEE <!--拼接sql --> <include refid="insert_set_column"/> <include refid="insert_values"/> </insert>
更新
<update id="update_1" parameterType="Employee"> update EMPLOYEE <set> <if test="ename != null and ename.trim().length>0">ENAME=#{ename},</if> <if test="job != null and job.trim().length>0">JOB=#{job},</if> <if test="mgr != null">MGR=#{mgr},</if> <if test="hiredate != null">HIREDATE=#{hiredate},</if> <if test="sal != null">SAL=#{sal},</if> <if test="comm != null">COMM=#{comm},</if> <if test="deptno != null">DEPTNO=#{deptno},</if> </set> <!-- <where>如果带多条件的更依然可以使<where>元素动态生成where子句</where> --> where EMPNO=#{empno} </update>
<update id="update_2" parameterType="Employee"> update EMPLOYEE <trim prefix="set" suffixOverrides=","> <if test="ename != null and ename.trim().length>0">ENAME=#{ename},</if> <if test="job != null and job.trim().length>0">JOB=#{job},</if> <if test="mgr != null">MGR=#{mgr},</if> <if test="hiredate != null">HIREDATE=#{hiredate},</if> <if test="sal != null">SAL=#{sal},</if> <if test="comm != null">COMM=#{comm},</if> <if test="deptno != null">DEPTNO=#{deptno},</if> </trim> <!-- <where>如果带多条件的更依然可以使<where>元素动态生成where子句</where> --> where EMPNO=#{empno} </update>
删除
<delete id="delete" parameterType="Employee"> delete EMPLOYEE EMPNO=#{empno} <!--条件多的话也可以使用<where>...</where> --> </delete>
高级使用
1.动态代理
我们之前,上面都是在Dao类中写上一段sqlsession.selectOne/selectList,还是比较麻烦
所以mybatis提供了一种简单的方法,使用动态代理(接口类)可以简化步骤
Mybatis中有这样的约定:
- 接口方法名与mapper中的id相同
- 接口方法参数与parameterType类型相同
- 接口方法的返回值类型与resultType类型相同
满足上面的条件,Mybatis就会将接口类中的方法和mapper中的sql语句一一对应起来,而不需要再次新建一个Dao,在Dao类里面编写方法
具体步骤:
1. 实体类编写
2. 新建接口类
如果方法的返回值为void,则mapper中就不需要定义resultType属性
如果方法返回值是List,mapper中的resultType为泛型T
package com.wan.mapping; import com.wan.bean.Employee; import java.util.List; /** * @author StarsOne * @date Create in 2019/9/16 0016 20:38 * @description */ public interface EmployeeMapper { List<Employee> selectAll(); }
2. 编写mapper.xml
<?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="com.wan.mapping.EmployeeMapper"> <!--特例:返回值为list,resultType=bean类--> <select id="selectAll" resultType="Employee" > select * from employee </select> </mapper>
3. 注册mapper
这里我们由于使用了package注册mapper,一定保证xxmapper.java和xxmapper.xml两个名字相同,大小写都要一样
保证Mapper.xml和接口的那个Mapper在相同的包路径,在mybatis配置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> <!--省略数据源配置 -->... <!-- 注册SQL映射文件,在这些文件中写SQL语句 --> <mappers> <!--指定整个包中的全部Mapper --> <package name="com.wan.mapper"/> </mappers> </configuration>
4. 使用
使用还是和之前一样,获得SqlSession对象,此对象有个getMapper方法,把接口类传入,就可以回调接口的方法了
Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader); SqlSession sqlSession = factory.openSession(); EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); List<Employee> employees = mapper.selectAll();
接口类中的方法名与EmployeeMapper.xml中的对应
使用:
EmployeeMapper mapper = sqlsession.getMapper(EmployeeMapper.class); mapper.selectById(7369);
2.遍历列表
Mybatis中提供了foreach标签,用于遍历
如果方法参数传入了一个List,可以使用此标签遍历,例子如下:
<!--相当于select * from employee where job in (...)) --> <select id="selectByJobs" parameterType="list" resultMap="baseResultMap"> select * from EMPLOYEE <where> <foreach item="job" collection="list" open="JOB IN(" close=")" separator=","> #{job} </foreach> </where> </select>
foreach标签的属性主要有 item,index,collection,open,separator,close,使用和JSTL标签里面的foreach标签差不多
属性名 | 说明 |
---|---|
item | 表示集合中每一个元素进行迭代时的别名 |
index | 指定一个名字,用于表示在迭代过程中,每次迭代到的位置, |
open | 表示该语句以什么开始, |
separator | 表示在每次进行迭代之间以什么符号作为分隔 符, |
close | 表示以什么结束。 |
关键属性:collection
- 如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
- 如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
- 如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可以封装成map,map的key就是参数名,所以这个时候collection属性值就是传入的List或array对象在自己封装的map里面的key
参考:mybatis 中 foreach collection的三种用法
3.考虑线程安全
使用ThreadLocal对象,保证每个线程取出的SqlSession是同一个对象
方法 | 说明 |
---|---|
void set(Object value) | 设置当前线程的线程局部变量的值。 |
public Object get() | 该方法返回当前线程所对应的线程局部变量。 |
public void remove() | 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。 |
protected Object initialValue() | 返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。 |
static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<SqlSession>(); //设置 threadLocal.set(sqlsession); //取出 SqlSession s = threadLocal.get();
4.嵌套查询
<!-- 结果集映射: 列《》属性名 --> <resultMap id="baseResultMap" type="Employee"> <!-- 专门映射主键列 --> <id property="empno" column="EMPNO" /> <result property="ename" column="ENAME"/> <result property="job" column="JOB"/> <result property="mgr" column="MGR"/> <result property="hiredate" column="HIREDATE"/> <result property="sal" column="SAL"/> <result property="comm" column="COMM"/> <result property="deptno" column="DEPTNO"/> </resultMap> <!-- 扩展另一个结果映射 --> <resultMap id="extendBaseResultMap" type="Employee" extends="baseResultMap"> <association property="department" javaType="Department"> <!-- 关联的嵌套结果 --> <id property="deptno" column="DEPTNO"/> <result property="dname" column="DNAME"/> <result property="location" column="LOC"/> </association> </resultMap> <!-- 1.嵌套结果(推荐使用) 优点:性能好,一条语句把所有实体的数据完全查询出来。 缺点:对SQL编写的要求高了,因为涉及多表连接查询 --> <select id="selectById" resultMap="extendBaseResultMap" parameterType="int"> select e.EMPNO, e.ENAME, e.JOB, e.MGR, e.HIREDATE, e.SAL, e.COMM, d.DEPTNO, d.DNAME, d.LOC from EMPLOYEE E inner join DEPARTMENT D on E.DEPTNO = D.DEPTNO where E.EMPNO=#{id} </select> <!-- 2. 嵌套查询 优点:编写SQL简单,无需做多表的连接查询;关联的实体通过单独的SQL语句查询并单独封装。 缺点:执行了N+1条件语句。性能差 --> <resultMap id="extendBaseResultMap_2" type="Employee" extends="baseResultMap"> <association property="department" column="DEPTNO" select="selectDepartmentById" /> </resultMap> <select id="selectDepartmentById" parameterType="int" resultType="Department"> select deptno, dname, loc as location from DEPARTMENT where DEPTNO=#{id} </select> <select id="selectById_2" resultMap="extendBaseResultMap_2" parameterType="int"> select e.EMPNO, e.ENAME, e.JOB, e.MGR, e.HIREDATE, e.SAL, e.COMM, e.DEPTNO from EMPLOYEE E where E.EMPNO=#{id} <!-- or e.empno=7902 or e.empno=7844 --> </select>
5.分页查询
分页的话,像以前那样使用三层嵌套查询也可以实现。
不过,有开发者为Mybatis开了个一个插件PageHelper,可以用来更为简单地使用分页查询
1.添加jar包
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <!--自动下载最新版本 --> <version>REALSE</version> </dependency>
2.配置拦截器插件
<!-- plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下: properties?, settings?, typeAliases?, typeHandlers?, objectFactory?,objectWrapperFactory?, plugins?, environments?, databaseIdProvider?, mappers? --> <plugins> <!-- com.github.pagehelper为PageHelper类所在包名 --> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <!-- 使用下面的方式配置参数,后面会有所有的参数介绍 --> <property name="param1" value="value1"/> </plugin> </plugins>
3.代码使用
只有在查询之前调用过startPage
或者是offsetPage
方法,后面的查询出来的List结果就会进行分页查询
下面的两个都是查询第一页,每一页有10条数据
//第二种,Mapper接口方式的调用,推荐这种使用方式。 PageHelper.startPage(1, 10); List<Employee> employees = employeeMapper.selectAll(); //第三种,Mapper接口方式的调用,推荐这种使用方式。 PageHelper.offsetPage(1, 10); List<Employee> employees = employeeMapper.selectAll();
这里提一下,这个插件还带有一个PageInfo
类,里面有可以记录各种信息
刚开始,我以为是和我之前自己封装的Page一样,详情请看Jsp学习笔记(4)——分页查询
但是,其实不一样的,这个PageInfo就是一个封装而已,只是用来存放数据而已,里面有各种信息
属性 | 说明 |
---|---|
pageNum | 当前页号(第几页) |
pageSize | 每页的显示的数据个数 |
size | 当前页的显示的数据个数 |
startRow | 当前页面第一个元素在数据库中的行号 |
endRow | 当前页面最后一个元素在数据库中的行号 |
pages | 总页数 |
prePage | 上一页的页号 |
nextPage | 下一页页号 |
isFirstPage | 是否为第一页 |
isLastPage | 是否为最后一页 |
hasPreviousPage | 是否有前一页 |
hasNextPage | 是否有下一页 |
navigatePages | 导航页码数 |
navigatepageNums | 所有导航页号 |
navigatePages | 导航条上的第一页 |
navigateFirstPage | 导航条上的第一页 |
navigateLastPage | 导航条上的最后一页 |
有个getTotal
方法,可以获得查询结果的总记录数
PageHelper.startPage(1, 10); List<Employee> employees = mapper.selectAll(); PageInfo<Employee> pageInfo = new PageInfo<>(employees);