Mybatis2-课堂笔记
一、Mybatis的参数和结果集
1. OGNL表达式了解
OGNL:Object Graphic Navigator Language,是一种表达式语言,用来从Java对象中获取某一属性的值。本质上使用的是JavaBean的getXxx()方法。
- 例如:
user.getUsername()—>user.username - 例如:
user.getAddress().getProvince()—>user.address.province - 例如:
list.size()>0,list != null
在#{}里、${}可以使用OGNL表达式,从JavaBean中获取指定属性的值
2. parameterType
2.1 简单类型
- 参数写法:
例如:int, double, short 等基本数据类型,或者string
或者:java.lang.Integer, java.lang.Double, java.lang.Short, java.lang.String
SQL语句里获取参数:
如果是一个简单类型参数,写法是:
#{随意}
2.2 POJO(JavaBean)
- 参数写法:
如果parameterType是POJO类型,要写全限定类名
例如:parameterType是com.itheima.domain.User
- SQL语句里获取参数:
#{JavaBean的属性名}
2.3 POJO包装类(复杂JavaBean)–QueryVO
- 参数写法:
在web应用开发中,通常有综合条件的搜索功能,例如:根据商品名称 和 所属分类 同时进行搜索。这时候通常是把搜索条件封装成JavaBean对象;JavaBean中可能还有JavaBean。
- SQL里取参数:
#{xxx.xx.xx}
2.3.1 功能需求
根据用户名搜索用户信息,查询条件放到QueryVO的user属性中。QueryVO如下:
public class QueryVO {
private User user;
//private String className;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}2.3.1 功能实现
1) 在映射器UserDao中增加方法
List<User> findByVO(QueryVO vo);2) 在映射配置文件中增加配置信息
<select id="findByVO" parameterType="com.itheima.domain.QueryVO" resultType="com.itheima.domain.User">
select * from user where username like #{user.username}
</select>3) 在单元测试类中编写测试代码
@Test
public void testFindByVO(){
QueryVO vo = new QueryVO();
User user = new User();
user.setUsername("%王%");
vo.setUser(user);
List<User> users = dao.findByVO(vo);
for (User user1 : users) {
System.out.println(user1);
}
}小结
parameterType里的写法:
简单类型,比如:8种基本数据类型和String。
- 如果只有一个简单类型的参数,SQL的
#{}里边可以随意写
<delete id="delete" parameterType="int"> delete from user where id = #{abc} </delete>- 如果只有一个简单类型的参数,SQL的
POJO对象,比如:
com.itheima.domain.User- SQL的
#{}里边要写JavaBean的属性名
<update id="edit" parameterType="com.itheima.domain.User"> update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id} </update>- SQL的
复杂POJO对象,比如:
com.itheima.domain.QueryVO- SQL的
#{}里边要写OGNL表达式,多点几次
<!--SQL语句里,要从QueryVO里获取user对象,从user对象里获取username--> <select id="searchByVo" parameterType="com.itheima.domain.QueryVO" resultType="com.itheima.domain.User"> select * from user where username like #{user.username} </select>- SQL的
3. resultType
注意:resultType是查询select标签上才有的,用来设置查询的结果集要封装成什么类型的
3.1 简单类型
例如:int, double, short 等基本数据类型,或者string
或者:java.lang.Integer, java.lang.Double, java.lang.Short, java.lang.String
3.2 POJO(JavaBean)
例如:com.itheima.domain.User
- 注意:JavaBean的属性名要和字段名保持一致
3.3 JavaBean中属性名和字段名不一致的情况处理
3.3.1 功能需求
有JavaBean类User2,属性名和数据库表的字段名不同。要求查询user表的所有数据,封装成User2的集合。其中User2如下:
public class User2 {
private Integer userId;
private String username;
private Date userBirthday;
private String userSex;
private String userAddress;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getUserBirthday() {
return userBirthday;
}
public void setUserBirthday(Date userBirthday) {
this.userBirthday = userBirthday;
}
public String getUserSex() {
return userSex;
}
public void setUserSex(String userSex) {
this.userSex = userSex;
}
public String getUserAddress() {
return userAddress;
}
public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}
@Override
public String toString() {
return "User2{" +
"userId=" + userId +
", username='" + username + '\'' +
", userBirthday=" + userBirthday +
", userSex='" + userSex + '\'' +
", userAddress='" + userAddress + '\'' +
'}';
}
}3.3.2 实现方案一:SQL语句中使用别名,别名和JavaBean属性名保持一致(用的相对少)
1) 在映射器UserDao中增加方法
/**
* JavaBean属性名和字段名不一致的情况处理---方案一
* @return
*/
List<User2> queryAll_plan1();2) 在映射配置文件UserDao.xml中增加statement
<select id="queryAll_plan1" resultType="com.itheima.domain.User2">
select id as userId, username as username, birthday as userBirthday, address as userAddress, sex as userSex from user
</select>3) 在单元测试类中编写测试代码
/**
* JavaBean属性名和字段名不一致的情况处理---方案一 单元测试代码
*/
@Test
public void testQueryAllUser2_plan1(){
List<User2> user2List = dao.queryAll_plan1();
for (User2 user2 : user2List) {
System.out.println(user2);
}
}3.3.3 实现方案二:使用resultMap配置字段名和属性名的对应关系(推荐)
1) 在映射器UserDao中增加方法
/**
* JavaBean属性名和字段名不一致的情况处理--方案二
* @return
*/
List<User2> queryAll_plan2();2) 在映射配置文件UserDao.xml中增加statement
<select id="queryAll_plan2" resultMap="user2Map">
select * from user
</select>
<!--
resultMap标签:设置结果集中字段名和JavaBean属性的对应关系
id属性:唯一标识
type属性:要把查询结果的数据封装成什么对象,写全限定类名
-->
<resultMap id="user2Map" type="com.itheima.domain.User2">
<!--id标签:主键字段配置。 property:JavaBean的属性名; column:字段名-->
<id property="userId" column="id"/>
<!--result标签:非主键字段配置。 property:JavaBean的属性名; column:字段名-->
<result property="username" column="username"/>
<result property="userBirthday" column="birthday"/>
<result property="userAddress" column="address"/>
<result property="userSex" column="sex"/>
</resultMap>3) 在单元测试类中编写测试代码
/**
* JavaBean属性名和字段名不情况处理--方案二 单元测试代码
*/
@Test
public void testQueryAllUser2_plan2(){
List<User2> user2List = dao.queryAll_plan2();
for (User2 user2 : user2List) {
System.out.println(user2);
}
}二、SqlMapConfig.xml核心配置文件
SqlMapConfig.xml中配置的内容和顺序如下:
properties(属性)
settings(全局配置参数)
typeAliases(类型别名) ★
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器) ★
1. typeAlias类型别名
在映射配置文件中,我们要写大量的parameterType和resultType,如果全部都写全限定类名的话,代码就太过冗余,开发不方便。可以使用类型别名来解决这个问题。
类型别名:是Mybatis为Java类型设置的一个短名称,目的仅仅是为了减少冗余。
注意:类型别名不区分大小写
1.1 内置别名
Mybatis提供的别名有:
| 别名 | 映射的类型 |
|---|---|
| _byte | byte |
| _long | long |
| _short | short |
| _int | int |
| _integer | int |
| _double | double |
| _float | float |
| _boolean | boolean |
| string | java.lang.String |
| byte | Byte |
| long | Long |
| short | Short |
| int | Integer |
| integer | Integer |
| double | Double |
| float | Float |
| boolean | Boolean |
| date | Date |
| decimal | BigDecimal |
| bigdecimal | BigDecimal |
| object | Object |
| map | Map |
| hashmap | HashMap |
| list | List |
| arraylist | ArrayList |
| collection | Collection |
| iterator | Iterator |
1.2 自定义类型别名
自己定义的JavaBean,全限定类名太长,可以自定义类型别名。
1.2.1 给一个类指定别名
1) 在SqlMapConfig.xml中配置一个类的别名
<typeAliases>
<!-- type:要指定别名的全限定类名 alias:别名 -->
<typeAlias type="com.itheima.domain.QueryVO" alias="vo"/>
<typeAlias type="com.itheima.domain.User" alias="user"/>
<typeAlias type="com.itheima.domain.User2" alias="user2"/>
</typeAliases>2) 在映射配置文件中使用类型别名
<!-- parameterType使用别名:vo, resultType使用别名:user -->
<select id="findByVO" parameterType="vo" resultType="user">
select * from user where username like #{user.username}
</select>1.2.2 指定一个包名
1) 在SqlMapConfig.xml中为一个package下所有类注册别名:类名即别名
<typeAliases>
<!-- 把com.itheima.domain包下所有JavaBean都注册别名,类名即别名,不区分大小写 -->
<package name="com.itheima.domain"/>
</typeAliases>2) 在映射配置文件中使用类型别名
<!-- parameterType使用别名:queryvo, resultType使用别名:user -->
<select id="findByVO" parameterType="queryvo" resultType="user">
select * from user where username like #{user.username}
</select>小结
- 如果自定义了大量的JavaBean,在映射文件里写起来就有大量的冗余
- 可以给这些JavaBean起别名:在核心配置文件里使用
<typeAliases>
<!--
<typeAlias type="全限定类名" alias="别名"></typeAlias>
<typeAlias type="全限定类名" alias="别名"></typeAlias>
-->
<!-- 把包里所有JavaBean自动注册别名。类名即别名 -->
<package name="包名"/>
</typeAliases>2. mappers映射器
用来配置映射器接口的配置文件位置,或者映射器接口的全限定类名
2.1 <mapper resource=""/>
用于指定映射配置文件xml的路径,支持xml开发方式,例如:
<mapper resource="com/itheima/dao/UserDao.xml"/>
注意:
映射配置文件的名称,和映射器接口类名 可以不同
映射配置文件的位置,和映射器接口位置 可以不同
配置了xml的路径,Mybatis就可以加载statement信息,并且根据namespace属性找到映射器
2.2 <mapper class=""/>
用于指定映射器接口的全限定类名,支持XML开发和注解开发,例如:
<mapper class="com.itheima.dao.UserDao"/>
如果是使用xml方式开发,那么要注意:
映射配置文件的名称 要和 映射器接口的类名相同
映射配置文件的位置 要和 映射器接口的位置相同
Mybatis只知道映射器的名称和位置,不知道配置文件的名称和位置。只能查找同名同路径的配置文件
2.3 <package name=""/>
用于自动注册指定包下所有的映射器接口,支持XML开发和注解开发,例如:
<package class="com.itheima.dao"/>
如果是使用XML方式开发,那么要注意:
映射配置文件的名称 要和 映射器接口的类名相同
映射配置文件的位置 要和 映射器接口的位置相同
Mybatis只能根据包名找到所有的映射器的类名和位置, 不知道配置文件的名称和位置。只能查找同名同路径的配置文件
小结
在实际开发中,通常是在核心配置文件里都加上:别名配置,mapper扫描
<typeAliases>
<package name=""/>
</typeAliases>
.......
<mappers>
<package name=""/>
</mappers>三、数据源和事务【了解】
1. Mybatis的数据源
Mybatis中的数据源,是指核心配置文件中<dataSouce></dataSouce>的配置。Mybatis为了提高数据库操作的性能,也使用了连接池的技术,但是它采用的是自己开发的连接池技术。
1.1 Mybatis中dataSouce的分类
1.1.1 三种dataSouce介绍
UNPOOLED:不使用连接池技术的数据源
对应Mybatis的
UnpooledDataSouce类,虽然也实现了javax.sql.DataSource接口,但是它的getConnection()方法中,并没有真正的使用连接池技术,而是直接从数据库中创建的连接对象,即:DriverManager.getConnection()方法POOLED:使用连接池技术的数据源
对应Mybatis的
PooledDataSouce类,它实现了javax.sql.DataSouce接口,同时采用了Mybatis自己开发的连接池技术,是我们使用最多的一种数据源JNDI:使用JNDI技术的数据源
采用服务器软件提供的JNDI技术实现,从服务器软件中获取数据源。从不同服务器软件中得到的
DataSouce对象不同。例如:Tomcat中配置了数据连接信息,我们的web应用部署到Tomcat里,就可以获取Tomcat中配置的数据源,而不需要web应用自己再配置数据库连接信息。
1.1.2 三种dataSouce的关系与源码分析
UnpooledDataSource和PooledDataSource都实现了javax.sql.DataSource接口UnpooledDataSource没有使用连接池技术,它的getConnection()方法是从数据库中创建的连接PooledDataSource采用了连接池技术- 它内部有
UnpooledDataSource的引用,当需要创建新连接时,是调用UnpooledDataSource来获取的 - 它只是提供了用于存放连接对象的池子
- 它内部有
1.1.3 PooledDataSource获取新连接的过程源码分析

1.2 Mybatis中dataSouce的配置
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis49"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource> 当Mybatis读取核心配置文件时,会根据我们配置的dataSource标签的type,来生成对应的dataSource对象
2. Mybatis的事务
因为Mybatis的是对JDBC的封装,所以Mybatis在本质上也是基于Connection对象实现的事务管理,只是把管理的代码封装起来了,是使用SqlSession对象进行事务管理的。
2.1 默认事务管理方式
默认情况下,我们使用工厂对象的openSession()方法得到的SqlSession对象,是关闭了事务自动提交的,即:默认情况下,SqlSession是开启了事务的,需要手动提交。
获取session对象:factory.openSession()
操作完数据库之后,需要手动提交事务:sqlSession.commit();
如果要回滚事务,就使用方法:sqlSession.rollback();
2.2 自动提交事务实现
Mybatis也支持自动提交事务,操作方法如下:
- 获取SqlSession对象:
factory.openSession(true) - 操作数据库,事务会自动提交
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
//获取的是自动提交事务的SqlSession,所以不需要再手动关闭事务
SqlSession session = factory.openSession(true);
UserDao dao = session.getMapper(UserDao.class);
//操作数据库
dao.delete(48);
//释放资源,事务会自动提交,所以不需要再手动提交事务
session.close();
is.close();- 将来事务,会交给Spring来进行管理。Mybatis的事务管理,了解即可
四、SQL深入-动态sql【重点】
1. Mybatis动态SQL简介
我们在前边的学习过程中,使用的SQL语句都非常简单。而在实际业务开发中,我们的SQL语句通常是动态拼接而成的,比如:条件搜索功能的SQL语句。
在Mybatis中,SQL语句是写在映射配置的XML文件中的。Mybatis提供了一些XML的标签,用来实现动态SQL的拼接。 常用的标签有:
<if></if>:用来进行判断,相当于Java里的if判断<where></where>:通常和if配合,用来代替SQL语句中的where 1=1<foreach></foreach>:用来遍历一个集合,把集合里的内容拼接到SQL语句中。例如拼接:in (value1, value2, ...)<sql></sql>:用于定义sql片段,达到重复使用的目的
2. Mybatis环境准备
我们以对user表的操作为例,演示这些标签的用法。先准备Mybatis的环境如下
创建Maven的java项目,配置好坐标,引入Mybatis的依赖
<!-- 项目坐标 --> <groupId>com.itheima</groupId> <artifactId>mybatis_day03_sql</artifactId> <version>1.0-SNAPSHOT</version> <!-- 引入jar包依赖 --> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>创建JavaBean实体类:User
public class User { private Integer id; private String username; private Date birthday; private String sex; private String address; //get/set... //toString... }创建映射器接口UserDao(准备好备用,暂时不需要加方法)
public interface UserDao { }创建映射配置文件UserDao.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.itheima.dao.UserDao"> </mapper>创建Mybatis的核心配置文件,配置好类型别名和映射器
<?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> <typeAliases> <package name="com.itheima.domain"/> </typeAliases> <environments default="mysql"> <environment id="mysql"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///mybatis"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <package name="com.itheima.dao"/> </mappers> </configuration>准备log4j日志配置文件
# Set root category priority to INFO and its only appender to CONSOLE. #log4j.rootCategory=INFO, CONSOLE debug info warn error fatal log4j.rootCategory=debug, CONSOLE, LOGFILE # Set the enterprise logger category to FATAL and its only appender to CONSOLE. log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE # CONSOLE is set to be a ConsoleAppender using a PatternLayout. log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n # LOGFILE is set to be a File appender using a PatternLayout. log4j.appender.LOGFILE=org.apache.log4j.FileAppender log4j.appender.LOGFILE.File=d:\axis.log log4j.appender.LOGFILE.Append=true log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n准备好单元测试类(准备好备用)
public class MybatisSqlTest { private InputStream is; private SqlSession session; private UserDao dao; @Before public void init() throws IOException { is = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); session = factory.openSession(); dao = session.getMapper(UserDao.class); } @After public void destory() throws IOException { session.close(); is.close(); } }
3. 动态SQL的标签应用
3.1 <if>标签:
3.1.1 语法介绍
<if test="判断条件,使用OGNL表达式进行判断">
SQL语句内容, 如果判断为true,这里的SQL语句就会进行拼接
</if>3.1.2 需求描述
根据用户的名称和性别搜索用户信息。
把搜索条件放到User对象里,传递给SQL语句
3.1.3 需求实现
1) 在映射器接口UserDao中增加方法
public interface UserDao {
//根据user搜索用户信息
List<User> search(User user);
}2) 在映射配置文件UserDao.xml中配置statement
<select id="search" parameterType="user" resultType="user">
select * from user where 1=1
<if test="username != null">
and username like #{username}
</if>
<if test="sex != null">
and sex = #{sex}
</if>
</select>注意:
- SQL语句中 where 1=1 不能省略
- 在if标签的test属性中,直接写OGNL表达式,从parameterType中取值进行判断,不需要加#{}或者${}
3) 在单元测试类中编写测试代码
@Test
public void testSearch(){
User user = new User();
user.setUsername("%王%");
user.setSex("男");
List<User> users = dao.search(user);
for (User u : users) {
System.out.println(u);
}
}3.1.4 小结
<if test="使用OGNL表达式进行判断">
如果为true,这里的内容会拼接SQL语句
</if>3.2 <where>标签
3.2.1 语法介绍
在刚刚的练习的SQL语句中,我们写了where 1=1。如果不写的话,SQL语句会出现语法错误。Mybatis提供了一种代替where 1=1的技术:<where></where>标签。
3.2.2 需求描述
把2.1.2中的实现代码 进行优化,使用<where></where>标签代替where 1=1
3.2.3 需求实现
只需要把刚刚的映射配置文件进行修改,修改后:
<select id="search" parameterType="user" resultType="user">
select * from user
<where>
<if test="username != null">
and username like #{username}
</if>
<if test="sex != null">
and sex = #{sex}
</if>
</where>
</select>注意:
<where>标签代替了where 1=1<where>标签内拼接的SQL没有变化,每个if的SQL中都有and- 使用
<where>标签时,Mybatis会自动处理掉条件中的第1个and,以保证SQL语法正确
再次运行测试代码,查看结果仍然正常
3.2.4 小结
<where>
<!-- 写SQL语句的where条件 -->
<if test="...">
and username like #{username}
</if>
<if test="...">
and sex = #{sex}
</if>
</where>3.3 <foreach>标签
3.3.1 语法介绍
foreach标签,通常用于循环遍历一个集合,把集合的内容拼接到SQL语句中。例如,我们要根据多个id查询用户信息,SQL语句:
select * from user where id = 1 or id = 2 or id = 3;
select * from user where id in (1, 2, 3); 假如我们传参了id的集合,那么在映射配置文件中,如何遍历集合拼接SQL语句呢?可以使用foreach标签实现。
<!--
foreach标签:
属性:
collection:被循环遍历的对象,使用OGNL表达式获取,注意不要加#{}
open:循环之前,拼接的SQL语句的开始部分
item:定义变量名,代表被循环遍历中每个元素,生成的变量名
separator:分隔符
close:循环之后,拼接SQL语句的结束部分
标签体:
使用#{OGNL}表达式,获取到被循环遍历对象中的每个元素
-->
<foreach collection="" open="id in(" item="id" separator="," close=")">
#{id}
</foreach>
id in(41,42,44,45)3.3.2 需求描述
QueryVO中有一个属性ids, 是id值的集合。根据QueryVO中的ids查询用户列表。
QueryVO类如下:
public class QueryVO {
private Integer[] ids;
public Integer[] getIds() {
return ids;
}
public void setIds(Integer[] ids) {
this.ids = ids;
}
}3.3.3 需求实现
1) 在映射器接口UserDao中增加方法
List<User> findByIds(QueryVO vo);2) 在映射配置文件UserDao.xml中添加statement
<!--在核心配置文件中已经使用package配置了类型别名-->
<select id="findByIds" resultType="user" parameterType="queryvo">
select * from user
<where>
<foreach collection="ids" open="and id in (" item="id" separator="," close=")">
#{id}
</foreach>
</where>
</select>3) 在单元测试类中编写测试代码
@Test
public void testFindUserByIdsQueryVO(){
QueryVO vo = new QueryVO();
vo.setIds(new Integer[]{41, 42});
List<User> userList = dao.findByIds(vo);
for (User user : userList) {
System.out.println(user);
}
}
3.3.4 小结
<foreach collection="OGNL获取被循环的对象" open="sql开始部分" item="变量名" separator="分隔符" close="sql结束部分">
#{变量名}
</foreach>
<foreach collection="ids" open="id in(" item="id" separator="," close=")">
#{id}
</foreach>3.4 <sql>标签
在映射配置文件中,我们发现有很多SQL片段是重复的,比如:select * from user。Mybatis提供了一个<sql>标签,把重复的SQL片段抽取出来,可以重复使用。
3.4.1 语法介绍
在映射配置文件中定义SQL片段:
<sql id="唯一标识">sql语句片段</sql>在映射配置文件中引用SQL片段:
<include refid="sql片段的id"></include>扩展:
如果想要引入其它映射配置文件中的sql片段,那么
<include>标签的refid的值,需要在sql片段的id前指定namespace。例如:
<include refid="com.itheima.dao.RoleDao.selectRole"></include> 表示引入了namespace为
com.itheima.dao.RoleDao的映射配置文件中id为selectRole的sql片段
3.4.2 需求描述
在查询用户的SQL中,需要重复编写:select * from user。把这部分SQL提取成SQL片段以重复使用
3.4.3 需求实现
1) 在映射配置文件UserDao.xml中定义SQL片段
<sql id="selectUser">
select * from user
</sql>2) 在映射配置文件UserDao.xml中使用SQL片段
<select id="findByIds" resultType="user" parameterType="queryvo">
<!-- refid属性:要引用的sql片段的id -->
<include refid="selectUser"></include>
<where>
<foreach collection="ids" open="and id in (" item="id" separator="," close=")">
#{id}
</foreach>
</where>
</select>五、Mybatis的缓存【了解】
1. 准备Mybatis环境
创建Maven的Java项目,配置依赖
创建JavaBean:User
注意:不要重写toString()方法,我们需要打印User对象的地址
public class User { private Integer id; private String username; private Date birthday; private String sex; private String address; //get/set方法...... }创建映射器接口UserDao
public interface UserDao { User findUserById(Integer uid); }创建映射配置文件UserDao.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.itheima.dao.UserDao"> <select id="findUserById" parameterType="int" resultType="user"> select * from user where id = #{uid} </select> </mapper>准备Mybatis的核心配置文件,配置好别名和映射器
<?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> <typeAliases> <package name="com.itheima.domain"/> </typeAliases> <environments default="mysql_mybatis"> <environment id="mysql_mybatis"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///mybatis"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <package name="com.itheima.dao"/> </mappers> </configuration>创建log4j日志配置文件
# Set root category priority to INFO and its only appender to CONSOLE. #log4j.rootCategory=INFO, CONSOLE debug info warn error fatal log4j.rootCategory=debug, CONSOLE, LOGFILE # CONSOLE is set to be a ConsoleAppender using a PatternLayout. log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n # LOGFILE is set to be a File appender using a PatternLayout. log4j.appender.LOGFILE=org.apache.log4j.FileAppender log4j.appender.LOGFILE.File=d:\axis.log log4j.appender.LOGFILE.Append=true log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
2. 一级缓存
2.1 什么是一级缓存
2.1.1 一级缓存介绍
- 一级缓存,是SqlSession对象提供的缓存(无论用不用,始终都有的)。
- 执行一次查询之后,查询的结果(JavaBean对象)会被缓存到SqlSession中。
- 再次查询同样的数据,Mybatis会优先从缓存中查找;如果找到了,就不再查询数据库。
2.1.2 一级缓存的清除
- 当调用了SqlSession对象的修改、添加、删除、commit()、close()、clearCache()等方法时,一级缓存会被清空。
2.2 一级缓存效果演示
/**
* Mybatis缓存效果演示
*/
public class MybatisCacheTest {
private InputStream is;
private SqlSession session;
private UserDao dao;
/**
* 测试 Mybatis的一级缓存:
* SQL语句执行了一次、输出user1和user2的地址是相同的
* 说明Mybatis使用了缓存
*/
@Test
public void testLevel1Cache(){
User user1 = dao.findUserById(41);
System.out.println(user1);
User user2 = dao.findUserById(41);
System.out.println(user2);
}
/**
* 测试 清除Mybatis的一级缓存
* 两次打印的User地址不同,执行了两次SQL语句
* SqlSession的修改、添加、删除、commit()、clearCache()、close()都会清除一级缓存
*/
@Test
public void testClearLevel1Cache(){
User user1 = dao.findUserById(41);
System.out.println(user1);
session.clearCache();
User user2 = dao.findUserById(41);
System.out.println(user2);
}
@Before
public void init() throws IOException {
is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
session = factory.openSession();
dao = session.getMapper(UserDao.class);
}
@After
public void destory() throws IOException {
session.close();
is.close();
}
}3. 二级缓存
3.1 什么是二级缓存
- 指Mybatis中SqlSessionFactory对象的缓存。
- 由同一个SqlSessionFactory对象生产的SqlSession对象,同样的映射器Mapper共享其缓存。
- 注意:
- 二级缓存,缓存的是序列化之后的数据;
- 当从缓存里取数据时,要进行反序列化还原成JavaBean对象
- 要求JavaBean必须实现序列化接口
Serializable - 二级缓存需要手动开启
3.2 二级缓存效果演示
1) 修改Mybatis核心配置文件,开启全局的二级缓存开关
<settings>
<!-- 增加此配置项,启动二级缓存(默认值就是true,所以这一步可以省略不配置) -->
<setting name="cacheEnabled" value="true"/>
</settings>2) 修改映射配置文件UserDao.xml,让映射器支持二级缓存
<mapper namespace="com.itheima.dao.UserDao">
<!-- 把cache标签加到映射配置文件 mapper标签里 -->
<cache/>
......
</mapper>3) 修改映射配置文件UserDao中的findById,让此方法(statement)支持二级缓存
<!-- 如果statement的标签上,设置有的useCache="true",表示此方法要使用二级缓存 -->
<select id="findById" parameterType="int" resultType="user" useCache="true">
select * from user where id = #{id}
</select>4) 修改JavaBean:User
注意:如果要使用二级缓存,那么JavaBean需要实现Serializable接口
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
//get/set......
}5) 编写测试代码
/**
* Mybatis二级缓存效果演示
*/
public class MybatisLevel2CacheTest {
private InputStream is;
private SqlSessionFactory factory;
/**
* 测试二级缓存。
* 测试结果:
* 虽然输出的user1和user2地址不同,但是SQL语句只执行了一次,说明第二次用了缓存。
* Mybatis的二级缓存,保存的不是JavaBean对象,而是散列的数据。
* 当要获取缓存时,把这些数据重新组装成一个JavaBean对象,所以地址不同
*/
@Test
public void testLevel2Cache(){
SqlSession session1 = factory.openSession();
UserDao dao1 = session1.getMapper(UserDao.class);
User user1 = dao1.findUserById(41);
System.out.println(user1);
session1.close();
SqlSession session2 = factory.openSession();
UserDao dao2 = session2.getMapper(UserDao.class);
User user2 = dao2.findUserById(41);
System.out.println(user2);
session2.close();
}
@Before
public void init() throws IOException {
is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(is);
}
@After
public void destory() throws IOException {
is.close();
}
}拓展了解:Mybatis实现传统开发CURD
Mybatis中提供了两种dao层的开发方式:一是使用映射器接口代理对象的方式;二是使用映射器接口实现类的方式。其中代理对象的方式是主流,也是我们主要学习的内容。
1. 相关类介绍
1.1 SqlSession
1.1.1 SqlSession简介
SqlSession是一个面向用户的接口,定义了操作数据库的方法,例如:selectList, selectOne等等。
每个线程都应该有自己的SqlSession对象,它不能共享使用,也是线程不安全的。因此最佳的使用范围是在请求范围内、或者方法范围内,绝不能将SqlSession放到静态属性中。
SqlSession使用原则:要做到SqlSession:随用随取,用完就关,一定要关
1.1.2 SqlSession的常用API
SqlSession操作数据库的常用方法有:
| 方法 | 作用 |
|---|---|
| selectList(String statement, Object param) | 查询多条数据,封装JavaBean集合 |
| selectOne(String statement, Object param) | 查询一条数据,封装JavaBean对象 查询一个数据,比如查询数量 |
| insert(String statement, Object param) | 添加数据,返回影响行数 |
| update(String statement, Object param) | 修改数据,返回影响行数 |
| delete(String statement, Object param) | 删除数据,返回影响行数 |
以上方法中的参数statment,是映射配置文件中的namespace 和 id的值方法名组成的。
例如:
映射配置文件的namespace值为com.itheima.dao.UserDao,执行的方法名是queryAll
那么statement的值就是:com.itheima.dao.UserDao.queryAll
1.2 SqlSessionFactory
是一个接口,定义了不同的openSession()方法的重载。SqlSessionFactory一旦创建后,可以重复使用,通常是以单例模式管理。
SqlSessionFactory使用原则:单例模式管理,一个应用中,只要有一个SqlSessionFactory对象即可。
1.3 SqlSessionFactoryBuilder
用于构建SqlSessionFactory工厂对象的。一旦工厂对象构建完成,就不再需要SqlSessionFactoryBuilder了,通常是作为工具类使用。
SqlSessionFactoryBuilder:只要生产了工厂,builder对象就可以垃圾回收了
1.4 API的小结
创建项目,导入依赖, 创建JavaBean
创建映射器接口UserDao,并创建实现类UserDaoImpl
public class UserDaoImpl implements UserDao{ private SqlSessionFactory factory; public UserDaoImpl(SqlSessionFactory factory){ this.factory = factory; } public List<User> queryAll(){ //1. 从工厂里获取SqlSession对象 SqlSession session = factory.openSession(); //2. 使用SqlSession的方法,操作数据库 List<User> list = session.selectList("com.itheima.dao.UserDao.queryAll"); //3. 关闭session session.close(); return list; } }创建映射配置文件
创建核心配置文件,日志配置文件
功能测试
- 实现类方式相关的API
- SqlSession对象:随用随取,用完就关,一定要关(类似于Connection)
- 查询得到集合:
selectList - 查询得到一个:
selectOne - 插入数据:
insert - 修改数据:
update - 删除数据:
delete
- 查询得到集合:
- SqlSessionFactory对象:单例模式,一个应用里只要一个工厂对象(类似于连接池)
- SqlSessionFactoryBuilder对象:用完就扔
- SqlSession对象:随用随取,用完就关,一定要关(类似于Connection)
2. 需求说明
针对user表进行CURD操作,要求使用映射器接口实现类的方式实现:
- 查询全部用户,得到
List<User>(上节课快速入门已写过,略) - 保存用户(新增用户)
- 修改用户
- 删除用户
- 根据主键查询一个用户,得到
User - 模糊查询
- 查询数量
3. 准备Mybatis环境
3.1 创建Maven的Java项目,准备JavaBean
1) 创建Maven的Java项目,坐标为:
<groupId>com.itheima</groupId>
<artifactId>day47_mybatis02_dao</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>2) 在pom.xml中添加依赖:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
</dependencies>3) 创建JavaBean
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}3.2 准备Mybatis的映射器和配置文件
1) 创建映射器接口UserDao(暂时不需要增加方法,备用)
public interface UserDao{
}2) 创建映射器接口的实现类UserDaoImpl
public class UserDaoImpl implements UserDao{
private SqlSessionFactory factory;
/**
* 构造方法。因为工厂对象,是整个应用只要一个就足够了,所以这里不要创建SqlSessionFactory对象
* 而是接收获取到工厂对象来使用。
*/
public UserDaoImpl(SqlSessionFactory factory) {
this.factory = factory;
}
}3) 创建映射配置文件UserDao.xml(暂时不需要配置statement,备用)
<?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.itheima.dao.UserDao">
</mapper>4) 创建Mybatis的核心配置文件
<?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>
<typeAliases>
<package name="com.itheima.domain"/>
</typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/itheima/dao/UserDao.xml"/>
</mappers>
</configuration>4) 准备log4j.properties日志配置文件
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=d:\axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
3.3 准备单元测试类
public class MybatisDaoCURDTest {
private InputStream is;
private SqlSessionFactory factory;
private UserDao dao;
@Before
public void init() throws IOException {
is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(is);
//创建Dao实现类对象时,把factory作为构造参数传递进去---一个应用只要有一个factory就够了
dao = new UserDaoImpl(factory);
}
@After
public void destory() throws IOException {
is.close();
}
}4. 编写代码实现需求
4.1 查询全部用户
1) 在映射器UserDao中增加方法
List<User> queryAll();2) 在映射配置文件UserDao.xml中增加statement
<select id="queryAll" resultType="User">
select * from user
</select>3) 在映射器实现类UserDaoImpl中实现方法
@Override
public List<User> queryAll() {
SqlSession session = factory.openSession();
List<User> users = session.selectList("com.itheima.dao.UserDao.queryAll");
session.close();
return users;
}4) 在单元测试类中编写测试代码
@Test
public void testQueryAll(){
List<User> users = dao.queryAll();
for (User user : users) {
System.out.println(user);
}
}4.2 保存/新增用户
1) 在映射器UserDao中增加方法
void save(User user);2) 在映射配置文件UserDao.xml中增加statement
<insert id="save" parameterType="User">
<selectKey resultType="int" keyProperty="id" order="AFTER">
select last_insert_id()
</selectKey>
insert into user (id, username, birthday, address, sex)
values (#{id}, #{username}, #{birthday},#{address},#{sex})
</insert>3) 在映射器实现类UserDaoImpl中实现方法
@Override
public void save(User user) {
SqlSession session = factory.openSession();
session.insert("com.itheima.dao.UserDao.save", user);
session.commit();
session.close();
}4) 在单元测试类中编写测试代码
@Test
public void testSaveUser(){
User user = new User();
user.setUsername("tom");
user.setAddress("广东深圳");
user.setBirthday(new Date());
user.setSex("男");
System.out.println("保存之前:" + user);
dao.save(user);
System.out.println("保存之后:" + user);
}4.2 修改用户
1) 在映射器UserDao中增加方法
void edit(User user);2) 在映射配置文件UserDao.xml中增加statement
<update id="edit" parameterType="User">
update user set username = #{username}, birthday = #{birthday},
address = #{address}, sex = #{sex} where id = #{id}
</update>3) 在映射器实现类UserDaoImpl中实现方法
@Override
public void edit(User user) {
SqlSession session = factory.openSession();
session.update("com.itheima.dao.UserDao.edit", user);
session.commit();
session.close();
}4) 在单元测试类中编写测试代码
@Test
public void testEditUser(){
User user = new User();
user.setId(71);
user.setUsername("jerry");
user.setAddress("广东深圳宝安");
user.setSex("女");
user.setBirthday(new Date());
dao.edit(user);
}4.3 删除用户
1) 在映射器UserDao中增加方法
void delete(Integer id);2) 在映射配置文件UserDao.xml中增加statement
<delete id="delete" parameterType="int">
delete from user where id = #{uid}
</delete>3) 在映射器实现类UserDaoImpl中实现方法
@Override
public void delete(Integer id) {
SqlSession session = factory.openSession();
session.delete("com.itheima.dao.UserDao.delete", id);
session.commit();
session.close();
}4) 在单元测试类中编写测试代码
@Test
public void testDeleteUser(){
dao.delete(71);
}4.4 根据主键查询一个用户
1) 在映射器UserDao中增加方法
User findById(Integer id);2) 在映射配置文件UserDao.xml中增加statement
<select id="findById" parameterType="int" resultType="User">
select * from user where id = #{id}
</select>3) 在映射器实现类UserDaoImpl中实现方法
@Override
public User findById(Integer id) {
SqlSession session = factory.openSession();
User user = session.selectOne("com.itheima.dao.UserDao.findById", id);
session.close();
return user;
}4) 在单元测试类中编写测试代码
@Test
public void testFindUserById(){
User user = dao.findById(48);
System.out.println(user);
}4.5 模糊查询
4.5.1 使用#{}方式进行模糊查询
1) 在映射器UserDao中增加方法
/**使用#{}方式进行模糊查询*/
List<User> findByUsername1(String username);2) 在映射配置文件UserDao.xml中增加statement
<!-- 使用#{}方式进行模糊查询 -->
<select id="findByUsername1" parameterType="string" resultType="User">
select * from user where username like #{username}
</select>3) 在映射器实现类UserDaoImpl中实现方法
@Override
public List<User> findByUsername1(String username) {
SqlSession session = factory.openSession();
List<User> users = session.selectList("com.itheima.dao.UserDao.findByUsername1", username);
session.close();
return users;
}4) 在单元测试类中编写测试代码
@Test
public void testFindUserByUsername1(){
List<User> users = dao.findByUsername1("%王%");
for (User user : users) {
System.out.println(user);
}
}4.5.2 使用${value}方式进行模糊查询
1) 在映射器UserDao中增加方法
/**使用${value}方式进行模糊查询*/
List<User> findByUsername2(String username);2) 在映射配置文件UserDao.xml中增加statement
<!-- 使用${value}方式进行模糊查询 -->
<select id="findByUsername2" parameterType="string" resultType="User">
select * from user where username like '%${value}%'
</select>3) 在映射器实现类UserDaoImpl中实现方法
@Override
public List<User> findByUsername2(String username) {
SqlSession session = factory.openSession();
List<User> users = session.selectList("com.itheima.dao.UserDao.findByUsername2", username);
session.close();
return users;
}4) 在单元测试类中编写测试代码
@Test
public void testFindUserByUsername2(){
List<User> users = dao.findByUsername2("王");
for (User user : users) {
System.out.println(user);
}
}4.6 查询数量
1) 在映射器UserDao中增加方法
Integer findTotalCount();2) 在映射配置文件UserDao.xml中增加statement
<select id="findTotalCount" resultType="int">
select count(*) from user
</select>3) 在映射器实现类UserDaoImpl中实现方法
@Override
public Integer findTotalCount() {
SqlSession session = factory.openSession();
Integer count = session.selectOne("com.itheima.dao.UserDao.findTotalCount");
session.close();
return count;
}4) 在单元测试类中编写测试代码
@Test
public void testFindTotalCount(){
Integer totalCount = dao.findTotalCount();
System.out.println(totalCount);
}5. 映射器接口代理对象方式和实现类方式运行原理
5.1 映射器接口代理对象的方式 运行过程
debug跟踪—>实现类传统方式的sqlsession的方法—>底层是JDBC的代码
5.2 映射器接口实现类的方式 运行过程
debug跟踪—>底层是JDBC的代码