MyBatis 是一个基于JDBC 的持久层框架。
和其它持久化层技术对比
在项目中使用 MyBatis 框架,需要在pom中引入依赖。
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>x.x.xversion>
dependency>
需要配置MyBatis 的核心配置文件。通过配置文件来获取 SqlSessionFactory 对象,SqlSessionFactory 用来创建SQL 的会话,通过SQL 会话进一步操作数据库。
SqlSession:代表Java程序和数据库之间的会话。
SqlSessionFactory:是生产SqlSession的工厂。
mybatis 核心配置文件,通常命名为 mybatis-config.xml
DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://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>
通过配置文件来获取 sqlSessionFactory。
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
创建mapper接口,也就是dao,mapper接口不需要提供实现类,只需要创建 mapper接口对应的配置文件,或者在接口上使用注解,就可以实现对数据库的操作。
public interface UserMapper {
/**
* 添加用户信息
*/
int insertUser();
}
创建 mapper 接口对应的映射文件。
ORM(Object Relationship Mapping)对象关系映射。
对象:Java的实体类对象。
关系:关系型数据库。
映射:二者之间的对应关系。
映射文件命名规则:
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.mapper.UserMapper">
<insert id = "insertUser">
insert into t_user values(null,'张三','123',23,'女')
insert>
mapper>
通过上面的配置就基本搭建了 Mybatis 的开发环境。
在程序中,通过核心配置文件创建出 sqlSessionFactory,通过sqlSessionFactory获取 sqlSession,通过sqlSession,获取一个mapper接口的代理对象。代理对象是一个接口的实例化对象,可以调用mapper接口中的实现方法,mapper接口的方法和对应的配置文件通过namespace和id属性绑定,就可以实现对数据库的操作。
public void testMyBatis() throws IOException {
//加载核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//获取SqlSessionFactoryBuilder
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//获取sqlSessionFactory
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//获取mapper接口对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//测试功能
int result = mapper.insertUser();
//提交事务
//sqlSession.commit();
System.out.println("result:"+result);
}
核心配置文件中的标签必须按照固定的顺序:
properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,
reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?
properties 标签,引入properties文件,此时就可以${属性名}的方式访问属性值
<properties resource="jdbc.properties">properties>
settings 标签可以设置一些属性,可以参考官方文档进行设置。
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
settings>
typeAliases 设置POJO类的别名。
typeAlias:设置某个具体的类型的别名。
type:需要设置别名的类型的全类名。
alias:设置此类型的别名,若不设置此属性,该类型拥有默认的别名,即类名且不区分大小写若设置此属性,此时该类型的别名只能使用alias所设置的值。
<typeAliases>
<typeAlias type="com.atguigu.mybatis.bean.User">typeAlias>
<typeAlias type="com.atguigu.mybatis.bean.User" alias="abc">typeAlias>
<package name="com.atguigu.mybatis.bean"/>
typeAliases>
environments 设置多个连接数据库的环境,default 设置默认使用的环境的 id。
environment 设置具体的连接数据库的环境信息,需要设置事务管理方式,数据源信息。
<environments default="mysql_test">
<environment id="mysql_test">
<transactionManager type="JDBC"/>
<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>
mappers 设置映入的映射文件。
<mappers>
<mapper resource="UserMapper.xml"/>
<package name="com.atguigu.mybatis.mapper"/>
mappers>
一个mapper映射文件对应一个接口,mapper文件中编写sql的标签的对应一个接口的方法。
对数据库的操作有增删改查,mapper文件中的编写sql的标签有查询 select标签,修改update标签,删除delete标签,添加 insert标签。
添加
<insert id="insertUser">
insert into t_user values(null,'admin','123456',23,'男')
insert>
删除
<delete id="deleteUser">
delete from t_user where id = 7
delete>
修改
<update id="updateUser">
update t_user set username='ybc',password='123' where id = 6
update>
查询单个
<select id="getUserById" resultType="com.atguigu.mybatis.bean.User">
select * from t_user where id = 2
select>
查询集合
<select id="getUserList" resultType="com.atguigu.mybatis.bean.User">
select * from t_user
select>
其中增删改的操作会返回更新的行数,查询操作会返回对应的数据。对于查询需要将返回的数据与对于的POJO类进行映射。
查询的标签 select 必须设置属性 resultType 或 resultMap,用于设置实体类和数据库表的映射关系。
resultType:自动映射,用于属性名和表中字段名一致的情况。
resultMap:自定义映射,用于一对多或多对一或字段名和属性名不一致的情况。
当查询的数据为多条时,不能使用实体类作为返回值,只能使用集合。
MyBatis获取参数值的两种方式:${} 和 #{}
${} 的本质就是字符串拼接,#{} 的本质就是占位符赋值。
${} 使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;
#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号;
在映射文件的sql标签中获取接口中传递的参数。
可以通过@Param注解标识mapper接口中的方法参数,会将这些参数放在map集合中,以@Param注解的 value属性值为键,以参数为值。
只需要通过 # {} 和 $ {} 访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号。
如果参数为实体类类型,通过 对象名.属性名 可以获取属性值。
查询一个实体类对象,通过id来查询一条数据。在接口中使用 @Param 注解,设置属性值。在映射文件中 通过 #{} 的方式获取参数。
resultType 设置 查询结果集的封装类型。其中 封装的类型属性要和表中查询的属性对应(可开启驼峰映射,数据库字段中的下划线转换为驼峰方式)。
<select id="getUserById" resultType="User">
select * from t_user where id = #{id}
select>
查询一个list集合,查询的数据为多条,则接口的返回值要设置为集合类型。
<select id="getUserList" resultType="User">
select * from t_user
select>
查询单个数据
<select id="getCount" resultType="int">
select count(id) from t_user
select>
查询一条数据为 map 集合,会自动将字段和值放入map集合中。
<select id="getUserToMap" resultType="map">
select * from t_user where id = #{id}
select>
查询多条数据为map集合
将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,此时可以将这些map放在一个list集合中获取。
<select id="getAllUserToMap" resultType="map">
select * from t_user
select>
${} 的本质就是字符串拼接,#{} 的本质就是占位符赋值。对于有些 sql 只能使用字符串拼接。
模糊查询,如果使#{} 会先进行占位符替换,就会被替换为 ‘%?%’ ,而占位符出现在字符串中,就不能进行值替换。
<select id="testMohu" resultType="User">
select * from t_user where username like '%${mohu}%'
select>
批量删除,如果使用 #{} 就会整个替换,并加上了单引号,就达不到想要的结果。
<delete id="deleteMore">
delete from t_user where id in (${ids})
delete>
动态设置表名,#{} 占位符替换后会加上单引号,就会出错。
<select id="getAllUser" resultType="User">
select * from ${tableName}
select>
添加功能获取自增的主键
useGeneratedKeys:设置使用自增的主键
keyProperty:指定将主键放入参数的某个属性中。
下面中,自增的主键就会放入 user 的 id 属性中。
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into t_user values(null,#{username},#{password},#{age},#{sex})
insert>
若字段名和实体类的属性不一致,可以通过resultMap 设置自定义映射。
resultMap:设置自定义映射,id 属性:表示自定义映射的唯一标识,type 属性:查询的数据要映射的实体类的类型。
子标签:
id:设置主键的映射关系。
result:设置普通字段的映射关系。
association:设置多对一的映射关系。
collection:设置一对多的映射关系。
属性:
property:设置映射关系中实体类中的属性名。
column:设置映射关系中表中的字段名。
<resultMap id="userMap" type="User">
<id property="id" column="id">id>
<result property="userName" column="user_name">result>
<result property="password" column="password">result>
<result property="age" column="age">result>
<result property="sex" column="sex">result>
resultMap>
<select id="testMohu" resultMap="userMap">
select id,user_name,password,age,sex from t_user where user_name like concat('%',#{mohu},'%')
select>
若字段名和实体类中的属性名不一致,但是字段名符合数据库的规则(使用_),实体类中的属性名符合Java的规则(使用驼峰),可以在配置文件中设置一个全局配置信息 mapUnderscoreToCamelCase,自动完成映射。
如:字段名user_name 会自动映射为 userName。
在员工信息中存储了部门的信息。
实体类结构如下:
class Emp{
// 基本属性
String id;
...
// 部门类
Dept dep;
}
可以使用 association 处理映射关系。
association 中 property 属性为实体类中的属性名,javaType 为映射的对象类型。子标签中的id和result 同 resultMap下的作用一致。
<resultMap id="empDeptMap" type="Emp">
<id column="eid" property="eid">id>
<result column="ename" property="ename">result>
<result column="age" property="age">result>
<result column="sex" property="sex">result>
<association property="dept" javaType="Dept">
<id column="did" property="did">id>
<result column="dname" property="dname">result>
association>
resultMap>
<select id="getEmpAndDeptByEid" resultMap="empDeptMap">
select emp.*,dept.* from t_emp emp left join t_dept dept on emp.did = dept.did where emp.eid = #{eid}
select>
使用分步查询完成多对一的映射。
分步查询:使用多个 sql ,共同来完成一个功能。
根据部门id,查出部门信息。
<select id="getEmpDeptByStep" resultType="Dept">
select * from t_dept where did = #{did}
select>
使用员工id,查询员工信息和对应的部门信息。先查出员工的所有信息,然后调用 getEmpDeptByStep 接口,将部门id 作为参数,查出部门信息,将部门信息封装到 dept 属性中。
<resultMap id="empDeptStepMap" type="Emp">
<id column="eid" property="eid">id>
<result column="ename" property="ename">result>
<result column="age" property="age">result>
<result column="sex" property="sex">result>
<association property="dept" select="com.atguigu.MyBatis.mapper.DeptMapper.getEmpDeptByStep" column="did">
association>
resultMap>
<select id="getEmpByStep" resultMap="empDeptStepMap">
select * from t_emp where eid = #{eid}
select>
一个部门包含多个员工,在部门实体类中有一个集合来存储所有的员工信息。
使用 collection 完成一对多的映射。collection 中的 property 属性 用来指明实体类中的属性名。ofType 属性,用来指明处理的集合属性中存储的数据的类型。子标签中的id,result 与 resultMap中的标签作用一致。
<resultMap id="deptEmpMap" type="Dept">
<id property="did" column="did">id>
<result property="dname" column="dname">result>
<collection property="emps" ofType="Emp">
<id property="eid" column="eid">id>
<result property="ename" column="ename">result>
<result property="age" column="age">result>
<result property="sex" column="sex">result>
collection>
resultMap>
<select id="getDeptEmpByDid" resultMap="deptEmpMap">
select dept.*,emp.* from t_dept dept left join t_emp emp on dept.did = emp.did where dept.did = #{did}
select>
分步查询
根据部门 id 查询员工信息。
<select id="getEmpListByDid" resultType="Emp">
select * from t_emp where did = #{did}
select>
查询出部门信息,使用部门id调用 getEmpListByDid 接口查询出所有员工信息。
collection中 fetchType用来设置是否延迟加载。select 属性查询的全标识,column 指明查询的参数。
<resultMap id="deptEmpStep" type="Dept">
<id property="did" column="did">id>
<result property="dname" column="dname">result>
<collection property="emps" fetchType="eager" select="com.atguigu.MyBatis.mapper.EmpMapper.getEmpListByDid" column="did">
collection>
resultMap>
<select id="getDeptByStep" resultMap="deptEmpStep">
select * from t_dept where did = #{did}
select>
分步查询的优点:可以实现延迟加载。延迟加载指当使用的时候才进行查询相关数据。
使用延迟加载必须在核心配置文件中设置全局配置信息:
lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载
aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载。
association和 collection中的fetchType属性设置当前的分步查询是否使用延迟加载,fetchType= lazy(延迟加载) | eager(立即加载)。
在核心配置文件的 settings 中进行配置。
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
settings>
Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题。
if 标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中的内容不会执行。
<select id="getEmpListByMoreTJ" resultType="Emp">
select * from t_emp where 1=1
<if test="ename != '' and ename != null">
and ename = #{ename}
if>
<if test="age != '' and age != null">
and age = #{age}
if>
<if test="sex != '' and sex != null">
and sex = #{sex}
if>
select>
where和if一般结合使用:
<select id="getEmpListByMoreTJ2" resultType="Emp">
select * from t_emp
<where>
<if test="ename != '' and ename != null">
ename = #{ename}
if>
<if test="age != '' and age != null">
and age = #{age}
if>
<if test="sex != '' and sex != null">
and sex = #{sex}
if>
where>
select>
trim用于去掉或添加标签中的内容
常用属性:
<select id="getEmpListByMoreTJ" resultType="Emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and">
<if test="ename != '' and ename != null">
ename = #{ename} and
if>
<if test="age != '' and age != null">
age = #{age} and
if>
<if test="sex != '' and sex != null">
sex = #{sex}
if>
trim>
select>
choose、when、otherwise相当于if…else if…else
可以没有otherwise标签。当满足一个when标签时,就结束。若所有when标签都不满足,有otherwise标签就执行otherwise标签。
<select id="getEmpListByChoose" resultType="Emp">
select <include refid="empColumns">include> from t_emp
<where>
<choose>
<when test="ename != '' and ename != null">
ename = #{ename}
when>
<when test="age != '' and age != null">
age = #{age}
when>
<when test="sex != '' and sex != null">
sex = #{sex}
when>
<when test="email != '' and email != null">
email = #{email}
when>
choose>
where>
select>
foreach 循环遍历,拼接sql。
属性:
<insert id="insertMoreEmp">
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null,#{emp.ename},#{emp.age},#{emp.sex},#{emp.email},null)
foreach>
insert>
<delete id="deleteMoreByArray">
delete from t_emp where eid in
<foreach collection="eids" item="eid" separator="," open="(" close=")">
#{eid}
foreach>
delete>
SQL片段,定义一段公共的sql片段,在使用的地方通过include标签进行引入。
<sql id="empColumns">
eid,ename,age,sex,did
sql>
select <include refid="empColumns">include> from t_emp
MyBatis的一级缓存
一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问。
使一级缓存失效的四种情况:
MyBatis的二级缓存
二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取。
二级缓存开启的条件:
- 在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置。
- 在映射文件中设置标签<cache />
<cache />
- 二级缓存必须在SqlSession关闭或提交之后有效
- 查询的数据所转换的实体类类型必须实现序列化的接口
使二级缓存失效的情况:
两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效。
二级缓存的相关配置
在mapper配置文件中添加的cache标签可以设置一些属性:
MyBatis缓存查询的顺序
正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的。
逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:Java实体类,Mapper接口,Mapper映射文件。
创建逆向工程的步骤
<dependencies>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.7version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-maven-pluginartifactId>
<version>1.3.0version>
<dependencies>
<dependency>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-coreartifactId>
<version>1.3.2version>
dependency>
<dependency>
<groupId>com.mchangegroupId>
<artifactId>c3p0artifactId>
<version>0.9.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.8version>
dependency>
dependencies>
plugin>
plugins>
build>
DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="DB2Tables" targetRuntime="MyBatis3Simple">
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis"
userId="root"
password="123456">
jdbcConnection>
<javaModelGenerator targetPackage="com.atguigu.mybatis.bean"
targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
javaModelGenerator>
<sqlMapGenerator targetPackage="com.atguigu.mybatis.mapper"
targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.atguigu.mybatis.mapper"
targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
javaClientGenerator>
<table tableName="t_emp" domainObjectName="Emp"/>
<table tableName="t_dept" domainObjectName="Dept"/>
context>
generatorConfiguration>
@Test
public void testMBG() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSession sqlSession = new SqlSessionFactoryBuilder().build(is).openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
EmpExample empExample = new EmpExample();
//创建条件对象,通过andXXX方法为SQL添加查询添加,每个条件之间是and关系
empExample.createCriteria().andEnameLike("a").andAgeGreaterThan(20).andDidIsNotNull();
//将之前添加的条件通过or拼接其他条件
empExample.or().andSexEqualTo("男");
List<Emp> list = mapper.selectByExample(empExample);
for (Emp emp : list) {
System.out.println(emp);
}
}
分页插件使用步骤
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.2.0version>
dependency>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">plugin>
plugins>
使用分页插件。
分页相关数据
PageInfo{
pageNum=8, pageSize=4, size=2, startRow=29, endRow=30, total=30, pages=8,
list=Page{count=true, pageNum=8, pageSize=4, startRow=28, endRow=32, total=30,
pages=8, reasonable=false, pageSizeZero=false},
prePage=7, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true,
hasNextPage=false, navigatePages=5, navigateFirstPage4, navigateLastPage8,
navigatepageNums=[4, 5, 6, 7, 8]
}
常用数据:
pageNum:当前页的页码
pageSize:每页显示的条数
size:当前页显示的真实条数
total:总记录数
pages:总页数
prePage:上一页的页码
nextPage:下一页的页码
isFirstPage/isLastPage:是否为第一页/最后一页
hasPreviousPage/hasNextPage:是否存在上一页/下一页
navigatePages:导航分页的页码数
navigatepageNums:导航分页的页码,[1,2,3,4,5]
示例代码
@Test
public void testPageHelper(){
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
// Page
// 查询之前设置分页
PageHelper.startPage(6, 4);
// list 为设置分页后,当前页的数据
List<Emp> list = mapper.selectByExample(null)
// 根据查询结果获取分页相关数据
PageInfo<Emp> page = new PageInfo<>(list, 5);
// list.forEach(emp -> System.out.println(emp));
// pageInfo 中包含所有分页相关的数据,可以返回前端,进行展示。
System.out.println(page);
} catch (IOException e) {
e.printStackTrace();
}
}