mybatis-generator-core-1.3.2.jar 一、Mybatis 入门案例
1、开发环境准备(Idea)
- 导入 MyBatis 框架的 jar 包、 MySQL 驱动包、log4j 的 jar 包
2. 导入 log4j 的配置文件(复制粘贴即可)
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> // 注意这里,自己使用的工具
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) n" />
</layout>
</appender>
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.apache.ibatis">
<level value="info" />
</logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>
注意:
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
这里的 <log4j:configuration xmlns:log4j=”http://jakarta.apache.org/log4j/"> 在 Idea 中会报错
解决办法:
改为:
代码语言:javascript复制<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
<log4j:configuration debug="true">
最终版:(Idea)
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
<log4j:configuration debug="true">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) n" />
</layout>
</appender>
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.apache.ibatis">
<level value="info" />
</logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>
2、创建测试表
代码语言:javascript复制-- 创建库
CREATE DATABASE test_mybatis;
-- 使用库
USE test_mybatis;
-- 创建表
CREATE TABLE tb_user(
uid INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50),
password VARCHAR(50),
age INT,
sex VARCHAR(20)
);
3、创建 javaBean
代码语言:javascript复制public class User {
private Integer uid;
private String userName;
private String password;
private Integer age;
private String sex;
....生成构造器,以及get、set、toString方法。
4、Mapper 接口
代码语言:javascript复制public interface UserMapper {
User getUserByUid(String uid);
}
5、创建 MyBatis 的全局配置文件
代码语言:javascript复制<?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="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test_mybatis"/> // 根据自己的配置
<property name="username" value="root"/>
<property name="password" value="root"/> // 根据自己的配置
</dataSource>
</environment>
</environments>
<!-- 引入SQL映射文件,Mapper映射文件 -->
<mappers>
<mapper resource="UserMapper.xml" />
</mappers>
</configuration>
6、创建 Mybatis 的 sql 映射文件
代码语言:javascript复制<?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.oy.online.mapper.UserMapper">
<!--
<select>: 定义查询语句
id : 设置SQL语句的唯一标识
resultType: 结果类型,即实体类的全限定名
-->
<select id="getUserByUid" resultType="com.oy.online.bean.User">
select uid, username, password, age, sex from tb_user where uid = #{uid}
</select>
</mapper>
7、测试
代码语言:javascript复制public class TestMybatis {
@Test
public void test() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
// getMapper():会通过动态代理动态生成UserMapper的代理实现类
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User userByUid = userMapper.getUserByUid("1");
System.out.println(userByUid);
}
}
二、MyBatis 全局配置文件
1、MyBatis 全局配置文件简介
文件结构如下:
2、properties 属性
- 创建一个资源文件,名为jdbc.properties 的文件,将四个连接字符串的数据在资源文件中通过键值 对(key=value)的方式放置 。
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test_mybatis
jdbc.username=root
jdbc.password=root
代码语言:javascript复制<!--
<properties >: 设置或引入资源文件
resource: 在类路径下访问资源文件
url: 在网络路径或磁盘路径下访问资源文件
-->
<properties resource="jdbc.properties"></properties>
- 在 environment 元素的 dataSource 元素中为其动态设置
<environments default="mysql">
<environment id="mysql">
<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>
3、settings 属性
代码语言:javascript复制<settings>
<!-- 将下划线映射成驼峰,user_name映射为userName -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 是否查询所有数据 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
4、typeAliases 别名处理
- 类型别名是为 Java 类型设置一个短的名字,可以方便我们引用某个类。 (typeAlias)
- 类很多的情况下,可以批量设置别名这个包下的每一个类创建一个默认的别名,就是简单类名小写 。(package)
<typeAliases>
<!--
为类型设置类型别名
type: Java 类型, 若只设置type, 默认的别名就是类型,且不区分大小写
-->
<!--<typeAlias type="com.oy.online.bean.User" alias="user"></typeAlias>-->
<package name="com.oy.online.bean"/>
</typeAliases>
- MyBatis 已经取好的别名
5、environments 环境配置
- 每种环境使用一个 evironments 标签进行匹配并指定唯一标识符
- 可以通过 environments 标签中的 defaul 属性指定一个环境的标识符来快速的切换环境。
<!--
<environments>:设置连接数据库的环境
default:设置默认使用的数据库环境
-->
<environments default="mysql">
.....
</environments>
- environment 指定具体环境 id:指定当前环境的唯一标识
- transactionManager
- type: JDBC | MANAGED | 自定义
- JDBC : 使用了 JDBC 的提交和回滚设置,依赖于从数据源的到的连接来管理事务范围。(JdbcTransactionFactory)
- MANAGED: 不提交或回滚一个连接、让容器管理事务的整个生命周期(比如 JEE 应用服务器的上下文。ManagedTransactionFactory )
- 自定义: 实现 TransationFactory 接口,type = 全类名/别名
5、dataSource
- type: UNPOOLED | POOLED | JNDI | 自定义
- UNPOOLED: 不使用连接池,UnpooledDataSourceFactory
- POOLED: 使用连接池, PooledDataSourceFactory
- JNDI: 在 EJB 或应用服务器这类容器中查找执行的数据源
- 自定义:实现 DataSourceFactory 接口 ,定义数据源的获取方式
提醒: 实际开发中我们使用 Spring 管理数据源,并进行事务控制的配置来覆盖上述的配置。
6、mappers 映射器
- mapper 逐个注册 SQL 映射文件
- resource : 引入类路径下的文件
- url : 引入网络路径或者是磁盘路径下的文件
- class : 引入 Mapper 接口。有 SQL 映射文件,要求 Mapper 接口与 SQL 映射文件同名同位置,没有 SQL 映射文件,使用注解在接口的方法上写 SQL 语句。
<mappers>
<mapper resource="EmployeeMapper.xml" />
<mapper class="com.OY.mybatis.dao.EmployeeMapper"/>
<package name="com.OY.mybatis.dao"/>
</mappers>
- 使用批量注册,这种方式要求 SQL 映射文件名必须和接口名相同并且在同一目录下 <package>
<mappers>
<package name="com.oy.online.Mapper"/>
</mappers>
三、Mybatis 映射文件(CRUD)
代码语言:javascript复制// namespace: 根据自己的接口来确定
<mapper namespace="com.oy.online.Mapper.UserMapper">
....
</mapper>
- SQL 映射文件有很少的几个顶级元素
- cache – 给定命名空间的缓存配置。
- cache-ref – 其他命名空间缓存配置的引用。
- resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
- sql – 可被其他语句引用的可重用语句块。
- insert – 映射插入语句
- update – 映射更新语句
- delete – 映射删除语句
- select – 映射查询语
1、select
Mapper 接口方法
代码语言:javascript复制// 根据eid 查询一个员工的信息
Emp getEmpByEid(String eid);
Mapper 映射文件
代码语言:javascript复制<select id="getEmpByEid" resultType="com.oy.online.bean.Emp">
select eid, ename, age, sex, did from emp where eid = #{eid}
</select>
2、insert
Mapper 接口方法
代码语言:javascript复制// 添加员工
void addEmp(Emp emp);
Mapper 映射文件
代码语言:javascript复制<insert id="addEmp" >
insert into emp values(null, #{ename}, #{age}, #{sex})
</insert>
3、update
Mapper 接口方法
代码语言:javascript复制// 修改员工信息
void updateEmp(Emp emp);
Mapper 映射文件
代码语言:javascript复制<!--void updateEmp(Emp emp);-->
<update id="updateEmp">
update emp set ename = #{ename}, age = #{age}, sex =#{sex} where eid = #{eid}
</update>
4、delete
Mapper 接口方法
代码语言:javascript复制<delete id="deleteEmp">
delete from emp where eid = #{eid}
</delete>
Mapper 映射文件
代码语言:javascript复制// 删除员工信息
Boolean deleteEmp(String eid);
四、Mybatis 映射文件(参数传递)
1、主键生成方式、获取主键值
- 主键生成方式
- 支持主键自增,例如 MySQL 数据库
- 不支持主键自增,例如 Oracle 数据库
- 获取主键值
- 若数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),则可以设置useGeneratedKeys=”true”,然后再把 keyProperty 设置到目标属性上。
Mapper 接口方法
//添加员工信息
void insertEmp(Emp emp);
Mapper 映射文件
代码语言:javascript复制<!--
useGeneratedKeys: 可以使用自动生成的主键
keyProperty: 将自动生成的主键赋值给传递过来的参数的哪一个属性
-->
<insert id="insertEmp" useGeneratedKeys="true" keyProperty="eid">
insert into emp values(null, '${ename}', ${age}, '${sex}')
</insert>
测试:
代码语言:javascript复制@Test
public void tes1() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true); //自动处理事务
ParaMpper mapper = sqlSession.getMapper(ParaMpper.class);
Emp emp = new Emp(null, "小沫沫", 12, "女");
mapper.insertEmp(emp);
System.out.println(emp.getEid());
}
2、 参数的传递
mybatis 获取参数值的两种方式:
- ${}: insert into emp values(null, admit, 23, 男)
Statement: 必须使用字符串拼接的方式操作 SQL,一定要注意单引号问题
- #{}:insert into emp values(null, ?,?,?)
PrepardStratement: 可以使通配符操作 SQL,因为在为 String 赋值时
使用建议:建议使用#{},在特殊情况下,需要使用${},例如模糊查询和分页查询
参数传递的方式:
- 当传递参数为单个 String 或基本数据类型和其他包装类
- #{} :可以以任意的名字获取参数值
- {} : 只能以{value} 或
- 当传输的参数为 JavaBean 时
- #{} 和 {} 都可以通过属性名直接获取属性值,但是要*注意{}的单引号问题。*
insert into emp values(null, '${ename}', ${age}, '${sex}')
- 当传递多个参数时,mybatis 会默认将这些参数放在 map 集合中
- 两种方式:
- 键为 0,1,2,3…N-1, 以参数为值
- 键为 param1, param2,param3….paramN, 以参数为值
- #{}:#{0},#{1};#{param1}、#{param2}
- {}: {param1}、{param2}, 但是要注意{}的单引号问题
<select id="getEmpByEidAndEname" resultType="Emp">
<!--select eid, ename, age, sex from emp where eid = #{0} and ename = #{1}-->
<!-- select eid, ename, age, sex from emp where eid = #{param1} and ename = #{param2}-->
select eid, ename, age, sex from emp where eid = '${param1}' and ename = '${param2}'
</select>
- 命名参数
- 为参数使用@Param 起一个名字,Mybatis 就会将这些参数封进 map 中, key 就是我们自己指的名字
Mapper 接口
代码语言:javascript复制Emp getEmpByEidAndEnameByParam(@Param("eid") String eid, @Param("ename") String ename);
Mapper 映射文件
代码语言:javascript复制<!--Emp getEmpByEidAndEnameByParam(@Param("eid") String eid, @Param("ename") String ename)-->
<select id="getEmpByEidAndEnameByParam" resultType="Emp">
select eid, ename, age, sex from emp where eid = #{eid} and ename = #{ename}
</select>
- Map 我们也可以封装多个参数为 Map,直接传递
Mapper 映射文件
代码语言:javascript复制<!--Emp getEmpByMap(Map<String, Object> map)-->
<select id="getEmpByMap" resultType="Emp">
select eid, ename, age, sex from emp where eid = ${eid} and ename = '${ename}'
</select>
_测试_:
代码语言:javascript复制@Test
public void test4() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
ParaMpper mapper = sqlSession.getMapper(ParaMpper.class);
HashMap<String, Object> map = new HashMap<>();
map.put("eid", "3");
map.put("ename", "小明");
Emp empByMap = mapper.getEmpByMap(map);
System.out.println(empByMap);
}
- Collection/Array
- 会被 MyBatis 封装成一个 map 传入, Collection 对应的 key 是 collection,Array 对应的 key 是 array. 如果确定是 List 集合, key 还可以是 list.
3、参数处理
- 参数位置支持的属性
javaType、 jdbcType、 mode、 numericScale、 resultMap、 typeHandler、 jdbcTypeName、expression
- 实际上通常被设置的是:可能为空的列名指定 jdbcType ,例如:
insert into orcl_employee(id,last_name,email,gender) values(employee_seq.nextval,#{lastName,jdbcType=NULL },#{email},#{gen der})
4、参数的获取方式
- #{key}: 获取参数的值,预编译到 SQL 中。安全。
- ${key}: 获取参数的值,拼接到 SQL 中。有 SQL 注入问题。
5、select 查询的几种情况
- 查询单行数据返回单个对象
Emp getEmpByEid(String eid);
- 查询数据表中的数量
Integer getCount();
- 查询多行数据返回对象的集合
public List<Employee> getAllEmps();
- 查询单行数据返回 Map 集合
Map<String, Object> getEmpMapByEid(String eid);
- 查询多行数据返回 Map 集合
@MapKey("eid") // 设置map的键,因为在查询时传出所有的员工信息,可以把员工信息作为值,但是必须设置键
Map<String, Object> getAllEmpMap();
6、resultMap 自定义映射
- 自定义 resultMap, 实现高级结果集映射。
- id:用于完成主键值的映射。
- result:用于完成普通列的映射。
- association: 一个复杂的类型关联;许多的结果将包装成这种类型
- collection: 复杂的类型集
<resultMap>:自定义映射,处理复杂的表关系
<id column="eid" property="eid"/>
<id>:设置主键的映射关系,column设置字段名,property设置属性名
<result column="ename" property="ename"/>
<result>:设置非主键的映射关系,column设置字段名,property设置属性名
① id&result
代码示例:
代码语言:javascript复制<select id="getEmployeeById" resultMap="myEmp">
select id, last_name,email, gender from tbl_employee where id =#{id}
</select>
<resultMap type="com.oy.mybatis.beans.Employee" id="myEmp">
<id column="id" property="id" />
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</resultMap>
② association (多对一)
- POJO 中的属性可能会是一个对象,我们可以使用联合查询,并以级联属性的方式封装对象.使用 association 标签定义对象的封装规则 。
public class Dept {
private Integer did;
private String dname;
...省略set、get方法
}
代码语言:javascript复制public class Emp {
private Integer eid;
private String ename;
private Integer age;
private String sex;
private Dept dept;
...省略set、get方法
}
- 使用级联的方式
<resultMap id="empMap" type="Emp">
<id column="eid" property="eid"/>
<result column="ename" property="ename"/>
<result column="age" property="age"/>
<result column="sex" property="sex"/>
<!--级联的方式-->
<result column="did" property="dept.did"/>
<result column="dname" property="dept.dname"/>
</resultMap>
<select id="getAllEmp" resultMap="empMap">
select e.eid, e.ename, e.age, e.sex, e.did, d.dname from emp e left join dept d on e.did = d.did
</select>
- Association
<resultMap id="empMap" type="Emp">
<id column="eid" property="eid"/>
<result column="ename" property="ename"/>
<result column="age" property="age"/>
<result column="sex" property="sex"/>
<association property="dept" javaType="Dept"> //javaType:"com.oy.online.bean.Dept"
<id column="did" property="did"/>
<result column="dname" property="dname"/>
</association>
</resultMap>
<select id="getAllEmp" resultMap="empMap">
select e.eid, e.ename, e.age, e.sex, e.did, d.dname from emp e left join dept d on e.did = d.did
</select>
③ association 分布查询
在世界开发中,对于每个实体类对都应该由具体的增删改查的方法,也就是 DAO 层,因此对于查询员工信息并将对应的部门信息也查询出来的需求,就可以通过分布的方式完成查询。
- 先通过员工的 id 查询员工的信息
- 在通过查询出来的员工信息的外键(部门 id) 查询对应的部门信息。
<mapper namespace="com.oy.online.Mapper.DeptMapper">
<!-- Dept getDeptByDid(String did);-->
<select id="getDeptByDid" resultType="Dept">
select did, dname from dept where did = #{did}
</select>
</mapper>
代码语言:javascript复制<resultMap id="empMapStep" type="Emp">
<id column="eid" property="eid"/>
<result column="ename" property="ename"/>
<result column="age" property="age"/>
<result column="sex" property="sex" />
<!--
select: 分布查询的SQL的id,即接口的全限定名、方法名或namespace.SQL的id
column: 分布查询的条件,注意:此条件必须是数据库查询过得
-->
<association property="dept" select="com.oy.online.Mapper.DeptMapper.getDeptByDid" column="did" />
</resultMap>
<!-- Emp getEmpStep(String eid);-->
<select id="getEmpStep" resultMap="empMapStep">
select eid, ename, age, sex, did from emp where eid = #{eid}
</select>
测试:
代码语言:javascript复制@Test
public void test2() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpDeptMapper mapper = sqlSession.getMapper(EmpDeptMapper.class);
Emp empStep = mapper.getEmpStep("1");
System.out.println(empStep);
}
④ association 分步查询使用延迟加载
在分布查询的基础上,可以使用延迟加载来提升查询的效率,只需要在全局的 Settings 中进行如下配置:
代码语言:javascript复制<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 设置加载的数据是按需还是全部 -->
<setting name="aggressiveLazyLoading" value="false"/>
⑤ collection (多对一或多对多)
- POJO 中的属性可能会是一个集合对象,我们可以使用联合查询,并以级联属性的方式封装对象.使用 collection 标签定义对象的封装规则 。
public class Dept {
private Integer did;
private String dname;
private List<Emp> emps;
...省略set、get方法
}
- Collection
<resultMap id="deptMap" type="Dept">
<id column="did" property="did"/>
<result column="dname" property="dname"/>
<!--
<collection>: 处理一对多和多对多的关系
ofType: 指集合中的类型,不需要指定javaType
-->
<collection property="emps" ofType="Emp">
<id column="eid" property="eid"/>
<result column="ename" property="ename"/>
<result column="age" property="age"/>
<result column="sex" property="sex"/>
</collection>
</resultMap>
<!-- Dept getDeptEmpsByDid(String did)-->
<select id="getDeptEmpsByDid" resultMap="deptMap" >
select d.did, d.dname, e.eid, e.ename, e.age, e.sex from dept d left join emp e on d.did = e.did where d.did = #{did}
</select>
⑥ collection 分步查询
实际的开发中,对于每个实体类都应该有具体的增删改查方法,也就是 DAO 层, 因此对于查询部门信息并且将对应的所有的员工信息也查询出来的需求,就可以通过分步的方式完成查询。
- 先通过部门的 id 查询部门信息
- 再通过部门 id 作为员工的外键查询对应的部门信息
<!--List<Emp> getEmpListByDid(String did);-->
<select id="getEmpListByDid" resultType="Emp">
select eid, ename, age, sex from emp where did= #{did}
</select>
<resultMap id="deprStep" type="Dept">
<id column="did" property="did"/>
<result column="dname" property="dname"/>
<collection property="emps" select="com.oy.online.Mapper.EmpDeptMapper.getEmpListByDid" column="did"></collection>
</resultMap>
<!--Dept getOnlyDeptByDid(String did);-->
<select id="getOnlyDeptByDid" resultMap="deprStep">
select did, dname from dept where did = #{did}
</select>
测试:
代码语言:javascript复制@Test
public void test4() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpDeptMapper mapper = sqlSession.getMapper(EmpDeptMapper.class);
Dept onlyDeptByDid = mapper.getOnlyDeptByDid("1");
System.out.println(onlyDeptByDid);
System.out.println("=============================");
System.out.println(onlyDeptByDid.getDname());
}
⑦ collection 分步查询使用延迟加载
代码语言:javascript复制<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 设置加载的数据是按需还是全部 -->
<setting name="aggressiveLazyLoading" value="false"/>
⑧ 扩展: 分步查询多列值的传递
- 如果分步查询时,需要传递给调用的查询中多个参数,则需要将多个参数封装成 Map 来进行传递,语法如下: {k1=v1, k2=v2….}
- 在所调用的查询方,取值时就要参考 Map 的取值方式,需要严格的按照封装 map 时所用的 key 来取值.
<collection property=”emps” select=”com.oy.online.Mapper.EmpDeptMapper.getEmpListByDid” column=”**{did=did}**”</collection>
⑨ 扩展: association 或 collection 的 fetchType 属性
- 在<association> 和<collection>标签中都可以设置 fetchType,指定本次查询是否要使用延迟加载。默认为 fetchType=”lazy” ,如果本次的查询不想使用延迟加载,则可设置为 fetchType=”eager”.
- fetchType 可以灵活的设置查询是否需要使用延迟加载,而不需要因为某个查询不想使用延迟加载将全局的延迟加载设置关闭.
<collection property=”emps” select=”com.oy.online.Mapper.EmpDeptMapper.getEmpListByDid” column=”did” **fetchType=”eager”** </collection>
五、Mybatis 动态 SQL
1、Mybatis 动态 SQL 简介
- 动态的 SQL 是 Mybatis 强大的特征之一。极大的简化我们拼装 SQL 的操作
- 动态 SQL 元素和使用 JSTL 或其类似基于 XML 的文本处理器相似
- Mybatis 采用功能强大的基于 OGNL 的表达式来简化
- if
- choose(when , otherwise)
- trim(where, set)
- foreach
- OGNL( Object Graph Navigation Language )对象图导航语言,这是一种强大的 表达式语言,通过它可以非常方便的来作对象属性。 类似于我们的 EL, SpEL 等。
描述 | 属性 |
---|---|
访问对象属性: | person.name |
调用方法: | person.getName() |
调用静态属性/方法: | @java.lang.Math@PI@java.util.UUID@randomUUID() |
调用构造方法: | new com.oy.bean.Person(‘admin’).name |
运算符: | ,-*,/,% |
逻辑运算符: | in,not in,>,>=,<,<=,==,!= |
注意:xml 中的特殊符号如:“>”,“<”等这些都需要使用转义字符 。
2、if where
- if 用于简单的判断
- Where 用于解决 SQL 语句中的 where 关键字以及条件中第一个 and 或者 or 的问题。(去掉多余的 and)
Mapper 接口
代码语言:javascript复制// 根据eid,ename, age, sex,多条件查询员工的信息
List<Emp> getEmpListByMultiter(Emp emp);
Mapper 映射
代码语言:javascript复制<select id="getEmpListByMultiter" resultType="Emp">
select eid, ename, age, sex from emp
<where>
<if test="eid != null">
and eid = #{eid}
</if>
<if test="ename != null and ename != ''">
and ename = #{ename}
</if>
<if test="age != null">
and age = #{age}
</if>
<if test="sex == 1 or sex == 2">
and sex = #{sex}
</if>
</where>
</select>
3、trim
- Trim 可以在条件判断完的 SQL 语句前后添加 或者去掉指定的字符
- prefix: 添加前缀(在操作的 SQL 语句前加入某些内容)
- prefixOverrides: 去掉前缀(把操作的 SQL 语句前的某些内容去掉)
- suffix: 添加后缀(在操作的 SQL 语句后加入某些内容)
- suffixOverrides: 去掉后缀 (把操作的 SQL 语句后的某些内容去掉)
Mapper 映射
代码语言:javascript复制<select id="getEmpListByMultiter" resultType="Emp">
select eid, ename, age, sex from emp
<trim prefix="where" suffixOverrides="and|or">
<if test="eid != null">
eid = #{eid} and
</if>
<if test="ename != null and ename != ''">
ename = #{ename} and
</if>
<if test="age !=null">
age = #{age} and
</if>
<if test="sex == 1 or sex == 0">
sex = #{sex}
</if>
</trim>
</select>
Mapper 接口
代码语言:javascript复制// 根据eid,ename, age, sex,多条件查询员工的信息
List<Emp> getEmpListByMultiter(Emp emp);
4、set
- set 主要用于解决修改操作中 SQL 语句中可能多出的逗号的问题
Mapper 映射
代码语言:javascript复制<update id="updateEmpByConditionSet">
update emp
<set>
<if test="ename != null and ename != ''">
ename = #{ename},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="sex == 1 or sex == 2">
sex = #{sex}
</if>
</set>
where eid = #{eid}
</update>
Mapper 接口
代码语言:javascript复制// 修改用户信息
void updateEmpByConditionSet(Emp emp);
5、Choose(when、otherwise)
choose 主要是用于分支判断,类似于 java 中的 switch case, 只会满足所有分支中的一个。
Mapper 映射
代码语言:javascript复制<select id="getEmpListByChoose" resultType="Emp">
select eid, ename, age, sex from emp
where
<choose>
<when test="eid != null">
eid = #{eid}
</when>
<when test="ename != null and ename = ''">
ename = #{ename}
</when>
<when test="age != null">
age = #{age}
</when>
<otherwise>
sex = #{sex}
</otherwise>
</choose>
</select>
Mapper 接口
代码语言:javascript复制// 根据eid ,eanem, age, sex中的其中一个查询一个员工信息
List<Emp> getEmpListByChoose(Emp emp);
6、foreach
- foreach 主要用于循环迭代
- collection: 指定要遍历的集合或数组
- item: 设置别名
- close: 设置循环体开始内容
- open: 设置循环体开始内容
- separator: 设置每一次循环之间的分隔符
- index: 若遍历的是 list, index 代表下标: 若遍历的是 map, index 代表键
① 批量删除
Mapper 接口
代码语言:javascript复制//通过list集合实现批量删除
void deleteMoreByList(@Param("eids")List<Integer> eids);
Mapper 映射
代码语言:javascript复制 <!--
delete from emp where eid in ();
delete from emp where eid = 1 or eid = 2 or eid = 3
-->
<delete id="deleteMoreByList">
delete from emp where
<foreach collection="eids" item="eid" separator="or" open="(" close=")">
eid = #{eid}
</foreach>
</delete>
<delete id="deleteMoreByList">
delete from emp where eid in
<foreach collection="eids" item="eid" separator="," open="(" close=")">
#{eid}
</foreach>
</delete>
② 批量查询
Mapper 接口
代码语言:javascript复制List<Emp> SelectMoreByList(@Param("eids")List<Emp> eids);
Mapper 映射
代码语言:javascript复制<select id="SelectMoreByList" resultType="Emp">
select eid , ename, age, sex from emp where
<foreach collection="eids" item="eid" separator="or" >
eid = #{eid}
</foreach>
</select>
<select id="SelectMoreByList1" resultType="Emp">
select eid , ename, age, sex from emp where eid in
<foreach collection="eids" item="eid" separator="," open="(" close=")">
#{eid}
</foreach>
</select>
③ 批量添加
Mapper 接口
代码语言:javascript复制void insertMoreByArray(@Param("emps")Emp[] emps);
Mapper 映射
代码语言:javascript复制<insert id="insertMoreByArray">
insert into emp values
<foreach collection="emps" item="emp" separator=",">
(null, #{emp.ename}, #{emp.age}, #{emp.sex},1)
</foreach>
</insert>
测试
代码语言:javascript复制@Test
public void test8() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = new Emp(null, "小小",12,"女");
Emp emp1 = new Emp(null, "小名",14,"女");
Emp emp2 = new Emp(null, "小正",17,"男");
Emp emp3 = new Emp(null, "小莫",12,"男");
Emp[] emps = {emp, emp1, emp2, emp3, emp3};
mapper.insertMoreByArray(emps);
}
③ 批量修改
注意:执行之前需要在 jdbc.properties 配置文件中的 url 最后配置 “**?allowMultiQueries=true**”
jdbc.url=jdbc:mysql://localhost:3306/test_mybatis**?allowMultiQueries=true**
Mapper 接口
代码语言:javascript复制void updateMoreByArray(@Param("emps")List<Emp> emps);
Mapper 映射
代码语言:javascript复制<update id="updateMoreByArray">
<foreach collection="emps" item="emp" >
update emp set ename = #{emp.ename}, age = #{emp.age},sex = #{emp.sex} where eid = #{emp.eid};
</foreach>
</update>
测试:
代码语言:javascript复制@Test
public void test9() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = new Emp(14, "小小",12,"女");
Emp emp1 = new Emp(15, "小明",14,"男");
Emp emp2 = new Emp(16, "小正",17,"男");
Emp emp3 = new Emp(17, "小莫",12,"男");
List list = new ArrayList();
list.add(emp);
list.add(emp1);
list.add(emp2);
list.add(emp3);
mapper.updateMoreByArray(list);
}
7、sql
- sql 标签是用于抽取可重复的 sql 片段,将相同的,使用频繁的 SQL 片段抽取出来,单独定义,方便多次引用
- 抽取 SQL
<sql id="selectSQL">
select eid, ename, age, sex from emp
</sql>
- 引用 SQL:
<include refid="selectSQL"></include>
六、Mybatis 缓存机制
1、缓存机制
- Mybatis 包含一个非常强大的查询的缓存特征,它可以非常方便地搭配配置和定制。缓存可以极大的提升查询效率
- Mybatis 系统中的默认定义了两极缓存。
- 一级缓存
- 二级缓存
- 默认情况下,只有一级缓存(Sqlsession 级别的缓存,也称为本地缓存)开启。
- 二级缓存需要手动开启配置,它是基于 namespace 级别的缓存(映射)。
- 为了提高扩展性。Mybatis 定义了缓存接口 Cache.我们可以通过实现 Cache 接口来自定义二级缓存。
2、一级缓存的使用
Mapper 接口
代码语言:javascript复制// 根据eid 来查询员工信息
Emp getEmpByEid(String eid);
Mapper 映射
代码语言:javascript复制<sql id="selectSQL">
select eid, ename, age, sex from emp
</sql>
<!--Emp getEmpByEid(String eid);-->
<select id="getEmpByEid" resultType="Emp">
<include refid="selectSQL"></include> where eid = #{eid}
</select>
测试
代码语言:javascript复制public SqlSessionFactory getSqlSessionFactory() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
return sqlSessionFactory;
}
@Test
public void test1() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp empByEid = mapper.getEmpByEid("14");
System.out.println(empByEid);
System.out.println("=================================");
Emp empByEid1 = mapper.getEmpByEid("14");
System.out.println(empByEid1);
}
说明:clearCache() 来清空本地缓存
3、一级缓存失效的几种情况
- 不同的 SqlSession 对应不同的一级缓存
- 同一个 SqlSession 但是查询条件不同
- 同一个 SqlSession 两次查询期间执行了任意一次增删改操作
- 同一个 SqlSession 两次查询期间手动清空了缓存
4、二级缓存的使用
- 二级缓存(second level cache),全局作用域缓存
- 二级缓存默认不开启,需要手动配置
- Mybatis 提供二级缓存的接口以及实现,缓存实现要求 POJO 实现 Serializable 接口
- 二级缓存在 SqlSession 关闭或提交之后才会生效
- 二级缓存使用步骤:
- 全局配置文件中开启二级缓存<setting name=”cacheEnabled” value=”true”/>
- 需要使用二级缓存的映射文件处使用 cache 配置缓存<cache />
- 注意:POJO 需要实现 Serializable 接口
代码示例:
代码语言:javascript复制 public SqlSessionFactory getSqlSessionFactory() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
return sqlSessionFactory;
}
@Test
public void test2() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp empByEid = mapper.getEmpByEid("14");
System.out.println(empByEid);
sqlSession.commit();
System.out.println("===============================");
Emp empByEid1 = mapper.getEmpByEid("14");
System.out.println(empByEid1);
}
- 二级缓存相关的属性
属性 | 描述 |
---|---|
eviction=“FIFO” | 缓存回收策略:LRU – 最近最少使用的:移除最长时间不被使用的对象。FIFO – 先进先出:按对象进入缓存的顺序来移除它们。SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。默认的是 LRU |
flushInterval | 刷新间隔,单位毫秒默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新 |
size | 引用数目,正整数代表缓存最多可以存储多少个对象,太大容易导致内存溢出 |
readOnly | 只读, true/falsetrue:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。 |
5、缓存的相关属性设置
- 全局 setting 的 cacheEnable: 配置二级缓存的开关,一级缓存一直打开的。
- select 标签的 userCache 属性: 配置这个 select 是否使用二级缓存。一级缓存一直使用的
- sql 标签的 flushCache 属性: 增删改默认 flushCache =true. sql 执行以后,会同时清空一级和二级缓存。 查询默认 flushCache = false。
- sqlSession.clearCache(): 只是用来清除一级缓存。
6、整合第三方缓存
- 为了提高扩展性。myBatis 定义了 缓存接口 Cache。我们可以通过实现 Cache 接口来自定义二级缓存
- 整合 E 和 Cache 缓存步骤: ① 导入 echcache 包,以及整合包,日志包
② 编写 ehcache,xml 配置文件
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:atguiguehcache" />
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
<!--
属性说明:
l diskStore:指定数据在磁盘中的存储位置。
l defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略
以下属性是必须的:
l maxElementsInMemory - 在内存中缓存的element的最大数目
l maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
l eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
l overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
以下属性是可选的:
l timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
l timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
l diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
l diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
l memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
-->
注意:IDEA 中新建 ehcache.xml 文件报错
- 报错语句
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
- 解决办法
- 打开 settings - languages&frameworks - schemas and dtds ,添加地址 http://ehcache.org/ehcache.xsd
- 将 “ ../config/ehcache.xsd ” 改成 “ http://ehcache.org/ehcache.xsd ”
③ 配置 cache 标签
代码语言:javascript复制<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
④ 测试
代码语言:javascript复制@Test
public void test3() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp empByEid = mapper.getEmpByEid("14");
System.out.println(empByEid);
sqlSession.commit();
System.out.println("====================================");
EmpMapper mapper1 = sqlSession.getMapper(EmpMapper.class);
Emp empByEid1 = mapper.getEmpByEid("6");
System.out.println(empByEid1);
sqlSession.commit();
System.out.println("====================================");
EmpMapper mapper2 = sqlSession.getMapper(EmpMapper.class);
Emp empByEid2 = mapper.getEmpByEid("7");
System.out.println(empByEid2);
}
七、MyBatis 逆向工程
1、逆向工程的配置
- 导入逆向工程的 jar 包 mybatis-generator-core-1.3.2.jar
- 编写 MBG 的配置文件(重要几处配置),可参考官方手册
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD
MyBatis3: 生成带条件的CRUD
-->
<context id="DB2Tables" targetRuntime="MyBatis3">
<!-- 设置连接数据库的信息 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/test_mybatis"
userId="root"
password="root">
</jdbcConnection>
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- javabean的生成策略 -->
<javaModelGenerator targetPackage="com.oy.online.bean" targetProject=".src">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- 映射文件的生成策略 -->
<sqlMapGenerator targetPackage="com.oy.online.Mapper" targetProject=".config">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.oy.online.Mapper" targetProject=".src">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 设置要将数据库中的哪张表逆向生成哪一个javabean -->
<table tableName="emp" domainObjectName="Emp"></table>
<table tableName="dept" domainObjectName="Dept"></table>
</context>
</generatorConfiguration>
- 运行代码生成器生成代码
@Test
public void testMBG() throws Exception {
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("mbg.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);
}
2、逆向工程的使用
- 基本查询(查询全部数据)
@Test
public void test2() throws Exception {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
// 不添加条件,即是查询所有数据
List<Emp> emps = mapper.selectByExample(null);
emps.forEach(System.out::println);
}
2. 带条件查询
代码语言:javascript复制@Test
public void test1() throws Exception {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
EmpExample empExample = new EmpExample();
EmpExample.Criteria c1 = empExample.createCriteria();
c1.andEnameLike("%a%");
c1.andSexEqualTo("1");
EmpExample.Criteria c2 = empExample.createCriteria();
c2.andDidEqualTo(2);
empExample.or(c2);
List<Emp> emps = mapper.selectByExample(empExample);
for (Emp emp : emps) {
System.out.println(emp);
}
}
八、扩展-PageHelper 分页插件
1、PageHelper 的使用步骤
- 导入相关包 pagehelper-x.x.x.jar 和 jsqlparser-0.9.5.jar
- 在 Mybatis 全局配置文件中配置分页插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
2、Page 对象的使用
- 载查询之前通过 PageHelps.startPage(页码,条数)设置分页信息,该方法返回 Page 对象
public SqlSessionFactory getsqlSessionFactory() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
return sqlSessionFactory;
}
@Test
public void testPageHelps() throws IOException {
SqlSessionFactory sqlSessionFactory = getsqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Page<Object> page = PageHelper.startPage(2, 3);
List<Emp> emps = mapper.getAllEmps();
emps.forEach(System.out::println);
System.out.println("====================================================");
System.out.println("当前页:" page.getPageNum());
System.out.println("总页码:" page.getPages());
System.out.println("总条数:" page.getTotal());
System.out.println("每条显示的条数:" page.getPageSize());
}
3、PageInfo 对象的使用
- 在查询完数据以后,使用 PageInfo 对象封装查询结果,可以获取更详细的分页信息以及完成分页逻辑
@Test
public void testPageHelps1() throws IOException {
SqlSessionFactory sqlSessionFactory = getsqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
// 设置分页信息
Page<Object> page = PageHelper.startPage(2, 3);
List<Emp> emps = mapper.getAllEmps();
PageInfo<Emp> info = new PageInfo<>(emps, 5);
for (Emp emp : emps) {
System.out.println(emp);
}
System.out.println("===================================");
System.out.println("当前页:" info.getPageNum());
System.out.println("总页数:" info.getPages());
System.out.println("每页显示的条数:" info.getPageSize());
System.out.println("是否是第一页:" info.isIsFirstPage());
System.out.println("是否显示最后一页:" info.isIsLastPage());
System.out.println("是否有上一页:" info.isHasPreviousPage());
System.out.println("是否有下一页:" info.isHasNextPage());
System.out.println("======================================");
int[] nums = info.getNavigatepageNums();
for (int num : nums) {
System.out.print(num " ");
}
}