第一节:Mybatis
1.1 概述
**框架:**是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为:框架是可被应用开发者定制的应用骨架。前者是从应用方面,而后者是从目的方面给出的定义。简而言之,框架其实 就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。简单来说就是使用别人搭好的平台,你来做表演,而且,框架一般是成熟的,不断升级的软件。
框架是我们软件开发中的一套解决方案,不同的框架解决的是不同的问题。
**使用框架的好处:**框架封装了很多细节,使开发者可以使用极简的方式实现功能,大大提高开发效率
1.2 三层架构
三层架构:
- 表现层(SpringMVC):用于展示数据的
- 业务层():处理业务需求
- 持久层(MyBatis):和数据库交互的
持久层技术解决方案:
JDBC技术:Connection
PreparedStatement
ResultSet
Spring的JdbcTemplate:Spring中对JDBC的简单封装
**Apache的DBUtils:**它和Spring的JdbcTemplate很像,也是对JDBC的简单封装
以上这些都不是框架,JDBC是规范,Spring的JdbcTemplate和Apache的DBUtils都只是工具类
1.3 MyBatis框架概述
MyBatis是一个优秀的基于Java的持久层框架,它内部封装了JDBC,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等复杂的过程
MyBatis通过xml或注解的方式将要执行的各种statement配置起来,并通过Java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为Java对象并返回
**采用ORM思想解决了实体和数据库映射的问题,对结果集进行了封装,**屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作
**ORM(Object Relation Mapping):**对象关系映射,简单地说,就是把数据库表和实体类的属性对应起来,让我们可以操作实体类就实现操作数据库表
1.4 mybatis的入门
mybatis的环境搭建:
- 创建maven工程并导入坐标
- 创建实体类和Dao的接口
- 创建mybatis的主配置文件(SqlMapConfig.xml)
- 创建映射配置文件(UserDaoImpl.xml)
环境搭建的注意事项:
- 创建
UserDaoImpl.xml
和UserDao.java
时,名称是为了和我们之前的知识保持一致,在mybatis中,他把持久层的接口名称和映像也叫做:Mapper,所以:UserDaoImpl.xml
和UserMapperImpl.xml
是一样的 - 在IDEA中创建目录的时候,他和包是不一样的,包在创建时:
com.itheima.dao
是三级结构;而目录在创建时:com.itheima.Dao
是一级结构 - mybatis的映射配置文件位置必须和Dao接口的包结构相同
- 映射配置文件的mapper标签namespace属性的取值必须是Dao接口的全限定类名
- 映射配置文件的操作配置,id属性的取值必须是Dao接口的方法名
当我们增强了第3、4、5点之后,我们在开发中就无须再写Dao的实现类
mybatis入门案例中的设计模式分析:
mybatis的入门案例:
-
读取配置文件
-
创建
SqlSessionFactory
工厂 -
创建
SqlSession
-
创建Dao接口的代理
-
执行Dao中的方法
-
释放资源
**注意事项:**不要忘记在映射配置中告知mybatis要封装到哪个实体类中
**配置的方式:**指定实体类的全限定类名
**mybatis基于注解的入门案例:**把UserDaoImpl.xml
移除,在Dao接口的方法上使用@select
注解,并且指定SQL语句,同时需要在SqlMapConfig.xml
中的mapper配置时,使用class属性指定Dao接口的全限定类名
明确:我们在实际开发中,都是越简便越好,所以都是采用不写Dao实现类的方式,不管使用xml还是注解配置,但是mybatis支持写Dao实现类
1.5 自定义mybatis的分析
mybatis在使用代理Dao的方式来实现增删改查时做什么事呢?
- 创建代理对象
- 在代理对象中调用
selectList
查询所有的分析:
自定义mybatis能通过入门案例看到的类:
class Resources
class SqlSessionFactoryBuilder
interface SqlSessionFactory
interface SqlSession
1.6 使用Mybatis完成CRUD
-
根据ID查询
-
在持久层接口中添加 findById 方法
User findById(Integer userId);
-
在用户的映射配置文件中配置
<!-- 根据 id 查询 --> <select id="findById" resultType="com.itheima.domain.User" parameterType="int"> select * from user where id = #{uid} </select>
细节:
resultType 属性:用于指定结果集的类型。
parameterType 属性:用于指定传入参数的类型。sql 语句中使用#{}字符:它代表占位符,相当于原来 jdbc 部分所学的?,都是用于执行语句时替换实际的数据。具体的数据是由#{}里面的内容决定的。
#{}中内容的写法:由于数据类型是基本类型,所以此处可以随意写。
-
-
保存操作
-
在持久层接口中添加新增方法
int saveUser(User user);
-
在用户的映射配置文件中配置
<!-- 保存用户--> <insert id="saveUser" parameterType="com.itheima.domain.User"> insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}) </insert>
细节:
parameterType 属性: 代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写类的全名称。 sql 语句中使用#{}字符: 它代表占位符,相当于原来 jdbc 部分所学的?,都是用于执行语句时替换实际的数据。 具体的数据是由#{}里面的内容决定的。
#{}中内容的写法: 由于我们保存方法的参数是 一个 User 对象,此处要写 User 对象中的属性名称。 它用的是 ognl 表达式。
ognl 表达式: 它是 apache 提供的一种表达式语言,全称是: Object Graphic Navigation Language 对象图导航语言 它是按照一定的语法格式来获取数据的。 语法格式就是使用 #{对象.对象}的方式;#{user.username}它会先去找 user 对象,然后在 user 对象中找到 username 属性,并调用 getUsername()方法把值取出来。但是我们在 parameterType 属性上指定了实体类名称,所以可以省略 user,而直接写 username
-
添加测试类中的测试方法
@Test public void testSave(){ User user = new User(); user.setUsername("modify User property"); user.setAddress("北京市顺义区"); user.setSex("男"); user.setBirthday(new Date()); System.out.println("保存操作之前:"+user); //5.执行保存方法 userDao.saveUser(user); System.out.println("保存操作之后:"+user); } 打开 Mysql 数据库发现并没有添加任何记录,原因是什么? 这一点和 jdbc 是一样的,我们在实现增删改时一定要去控制事务的提交,那么在 mybatis 中如何控制事务 提交呢? 可以使用:session.commit();来实现事务提交。加入事务提交后的代码如下: @After//在测试方法执行完成之后执行 public void destroy() throws Exception{ session.commit(); //7.释放资源 session.close(); in.close(); }
-
问题扩展:新增用户 id 的返回值
新增用户后,同时还要返回当前新增用户的 id 值,因为 id 是由数据库的自动增长来实现的,所以就相 当于我们要在新增后将自动增长 auto_increment 的值返回。 <insert id="saveUser" parameterType="USER"> <!-- 配置保存时获取插入的 id --> <selectKey keyColumn="id" keyProperty="id" resultType="int"> select last_insert_id(); </selectKey> insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}) </insert>
-
-
用户更新
-
在持久层接口中添加更新方法
int updateUser(User user);
-
在用户的映射配置文件中配置
<!-- 更新用户 --> <update id="updateUser" parameterType="com.itheima.domain.User"> update user set username=#{username},birthday=#{birthday},sex=#{sex}, address=#{address} where id=#{id} </update>
-
加入更新的测试方法
@Test public void testUpdateUser()throws Exception{ //1.根据 id 查询 User user = userDao.findById(52); //2.更新操作 user.setAddress("北京市顺义区"); int res = userDao.updateUser(user); System.out.println(res); }
-
-
用户删除
-
在持久层接口中添加删除方法
int deleteUser(Integer userId);
-
在用户的映射配置文件中配置
<!-- 删除用户 --> <delete id="deleteUser" parameterType="java.lang.Integer"> delete from user where id = #{uid} </delete>
-
加入删除的测试方法
@Test public void testDeleteUser() throws Exception { //6.执行操作 int res = userDao.deleteUser(52); System.out.println(res); }
-
-
用户模糊查询
-
在持久层接口中添加模糊查询方法
List<User> findByName(String username);
-
在用户的映射配置文件中配置
<!-- 根据名称模糊查询 --> <select id="findByName" resultType="com.itheima.domain.User" parameterType="String"> select * from user where username like #{username} </select>
-
加入删除的测试方法
@Test public void testFindByName(){ //5.执行查询一个方法 List<User> users = userDao.findByName("%王%"); for(User user : users){ System.out.println(user); } }
-
模糊查询的另一种配置方式
第一步:修改 SQL 语句的配置,配置如下: <!-- 根据名称模糊查询 --> <select id="findByName" parameterType="string" resultType="com.itheima.domain.User"> select * from user where username like '%${value}%' </select> 我们在上面将原来的#{}占位符,改成了${value}。注意如果用模糊查询的这种写法,那么${value}的写 法就是固定的,不能写成其它名字。 第二步:测试,如下: /** * 测试模糊查询操作 */ @Test public void testFindByName(){ //5.执行查询一个方法 List<User> users = userDao.findByName("王"); for(User user : users){ System.out.println(user); } }
-
1.6.eg #{}与${}的区别
#{}表示一个占位符号
通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换, #{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类 型值,#{}括号中可以是 value 或其它名称。
${}表示拼接 sql 串
通过${}可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换, 可以接收简单类型值或 p o j o 属性值,如果 p a r a m e t e r T y p e 传输单个简单类型值, {}可以接收简 单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值, 可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,{}括号中只能是 value
1.7 MyBatis中的参数深入
OGNL表达式:Object Graphic Navigation Language
-对象图导航语言
**作用:**它是通过对象的取值方法来获取数据,在写法上把get省略了。比如:我们获取用户的名称,类中的写法:user.getUsername();
,OGNL表达式写法:user.username
mybatis中为什么能直接写username而不用user?因为在parameterType中已经提供了属性所属的类,所以此时不需要写对象名
传递 pojo 包装对象:
开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查 询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。
Pojo 类中包含 pojo。
需求:根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中。
-
编写 QueryVo
public class QueryVo implements Serializable { private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } }
-
编写持久层接口
public interface IUserDao { /** * 根据 QueryVo 中的条件查询用户 * @param vo * @return */ List<User> findByVo(QueryVo vo); }
-
持久层接口的映射文件
<!-- 根据用户名称模糊查询,参数变成一个 QueryVo 对象了 --> <select id="findByVo" resultType="com.itheima.domain.User" parameterType="com.itheima.domain.QueryVo"> select * from user where username like #{user.username}; </select>
-
测试包装类作为参数
@Test public void testFindByQueryVo() { QueryVo vo = new QueryVo(); User user = new User(); user.setUserName("%王%"); vo.setUser(user); List<User> users = userDao.findByVo(vo); for(User u : users) { System.out.println(u); } }
1.8 Mybatis 的输出结果封装
resultType 配置结果类型
resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型。
我们在前面的 CRUD 案例中已经对此属性进行过应用了。
需要注意的是,它和 parameterType 一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须 使用全限定类名。例如:我们的实体类此时必须是全限定类名(今天最后一个章节会讲解如何配置实体类的别名)
同时,当是实体类名称是,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法 实现封装。
基本类型示例
-
Dao 接口
int findTotal();
-
映射配置
<!-- 查询总记录条数 --> <select id="findTotal" resultType="int"> select count(*) from user; </select>
实体类类型示例
-
Dao 接口
List<User> findAll();
-
映射配置
<!-- 配置查询所有操作 --> <select id="findAll" resultType="com.itheima.domain.User"> select * from user </select>
特殊情况示例
-
修改实体类
实体类代码如下:(此时的实体类属性和数据库表的列名已经不一致了) /** * * <p>Title: User</p> * <p>Description: 用户的实体类</p> * <p>Company: http://www.itheima.com/ </p> */ public class User implements Serializable { 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 "User [userId=" + userId + ", userName=" + userName + ", userBirthday=" + userBirthday + ", userSex=" + userSex + ", userAddress=" + userAddress + "]"; } }
-
Dao 接口
List<User> findAll();
-
映射配置
<!-- 配置查询所有操作 --> <select id="findAll" resultType="com.itheima.domain.User"> select * from user </select>
-
测试查询结果
@Test public void testFindAll() { List<User> users = userDao.findAll(); for(User user : users) { System.out.println(user); } }
-
修改映射配置
使用别名查询 <!-- 配置查询所有操作 --> <select id="findAll" resultType="com.itheima.domain.User"> select id as userId,username as userName,birthday as userBirthday, sex as userSex,address as userAddress from user </select>
resultMap 结果类型
resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。
在 select 标签中使用 resultMap 属性指定引用即可。同时 resultMap 可以实现将查询结果映射为复杂类 型的 pojo,比如在查询结果映射对象中包括 pojo 和 list 实现一对一查询和一对多查询。
-
定义 resultMap
<!-- 建立 User 实体和数据库表的对应关系type 属性:指定实体类的全限定类名,id 属性:给定一个唯一标识,是给查询 select 标签引用用的。 --> <resultMap type="com.itheima.domain.User" id="userMap"> <id column="id" property="userId"/> <result column="username" property="userName"/> <result column="sex" property="userSex"/> <result column="address" property="userAddress"/> <result column="birthday" property="userBirthday"/> </resultMap> id 标签:用于指定主键字段 result 标签:用于指定非主键字段 column 属性:用于指定数据库列名 property 属性:用于指定实体类属性名称
-
映射配置
<!-- 配置查询所有操作 --> <select id="findAll" resultMap="userMap"> select * from user </select>
-
测试结果
@Test public void testFindAll() { List<User> users = userDao.findAll(); for(User user : users) { System.out.println(user); } }
1.9 SqlMapConfig.xml配置文件
配置内容
-
SqlMapConfig.xml 中配置的内容和顺序
-properties(属性) --property -settings(全局配置参数) --setting -typeAliases(类型别名) --typeAliase --package -typeHandlers(类型处理器) -objectFactory(对象工厂) -plugins(插件) -environments(环境集合属性对象) --environment(环境子属性对象) ---transactionManager(事务管理) ---dataSource(数据源) -mappers(映射器) --mapper --package
-
properties(属性)
在使用 properties 标签配置时,我们可以采用两种方式指定属性配置。
-
<properties> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="1234"/> </properties>
-
在 classpath 下定义 db.properties 文件
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC jdbc.username=root jdbc.password=1234
properties 标签配置
<!-- 配置连接数据库的信息 resource 属性:用于指定 properties 配置文件的位置,要求配置文件必须在类路径下 resource="jdbcConfig.properties" url 属性: URL: Uniform Resource Locator 统一资源定位符 http://localhost:8080/mystroe/CategoryServlet URL 协议 主机 端口 URI URI:Uniform Resource Identifier 统一资源标识符 /mystroe/CategoryServlet 它是可以在 web 应用中唯一定位一个资源的路径 --> <properties url= "file:///D:\Java\JavaWeb\MyBatis\mybatis_Dao\src\main\resources\jdbcConfig.properties"> </properties>
dataSource 标签就变成了引用上面的配置
<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>
-
1.10 typeAliases(类型别名)
在前面我们讲的 Mybatis 支持的默认别名,我们也可以采用自定义别名方式来开发。
自定义别名
在 SqlMapConfig.xml 中配置:
<typeAliases>
<!-- 单个别名定义 -->
<typeAlias alias="user" type="com.itheima.domain.User"/>
<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) -->
<package name="com.itheima.domain"/>
<package name="其它包"/>
</typeAliases>
mappers(映射器)
-
<mapper resource=" " />
使用相对于类路径的资源 如:<mapper resource="com/itheima/dao/IUserDao.xml" />
-
<mapper class=" " />
使用 mapper 接口类路径 如:<mapper class="com.itheima.dao.UserDao"/> 注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
-
<package name=""/>
注册指定包下的所有 mapper 接口 如:<package name="cn.itcast.mybatis.mapper"/> 注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
1.11 mybatis的连接池及事务
连接池
我们在实际开发中都会使用连接池,因为它可以减少我们获取连接所消耗的时间
mybatis中的连接池
mybatis连接池提供了3种方式的配置:
配置位置:
-
主配置文件
SqlMapConfig.xml
中的dataSource
标签,type属性就是表示采用何种连接池方式取值:
POOLED
:采用传统的javax.sql.DataSource
规范中的连接池,mybatis中有针对规范的实现
UNPOOLED
:采用传统的获取连接的方式,虽然也实现了javax.sql.DataSource
接口,但是没有使用池的思想
JNDI
:采用服务器提供的JNDI技术实现,来获取DataSource
对象,不同服务器所能拿到的DataSource
是不一样的,同时需要注意,如果不是web或者maven的war工程,是不能使用的,我们使用的是tomcat服务器,采用的连接池就是dbcp连接池- Mybatis 中数据源的配置
我们的数据源配置就是在 SqlMapConfig.xml 文件中,具体配置如下: <!-- 配置数据源(连接池)信息 --> <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> MyBatis 在初始化时,根据<dataSource>的 type 属性来创建相应类型的的数据源 DataSource,即: type=”POOLED”:MyBatis 会创建 PooledDataSource 实例 type=”UNPOOLED” : MyBatis 会创建 UnpooledDataSource 实例 type=”JNDI”:MyBatis 会从 JNDI 服务上查找 DataSource 实例,然后返回使用
- Mybatis 中 DataSource 的存取
MyBatis 是 通 过 工 厂 模 式 来 创 建 数 据 源 DataSource 对 象 的 , MyBatis 定 义 了 抽 象 的 工 厂 接 口:org.apache.ibatis.datasource.DataSourceFactory,通过其 getDataSource()方法返回数据源 DataSource。 下面是 DataSourceFactory 源码,具体如下: package org.apache.ibatis.datasource; import java.util.Properties; import javax.sql.DataSource; /** * @author Clinton Begin */ public interface DataSourceFactory { void setProperties(Properties props); DataSource getDataSource(); } MyBatis 创建了 DataSource 实例后,会将其放到 Configuration 对象内的 Environment 对象中, 供 以后使用。
mybatis中事务原理和自动提交设置
事务:
事务的四大特性: ACID 原子性、
不考虑隔离性会产生的3个问题
**解决办法:**四种隔离级别
mybatis中的事务是通过SqlSession
对象的commit
方法和rollback
方法实现事务的提交和回滚
-
Mybatis 中事务提交方式
Mybatis 中事务的提交方式,本质上就是调用 JDBC 的 setAutoCommit()来实现事务控制。 我们运行之前所写的代码: @Test public void testSaveUser() throws Exception { User user = new User(); user.setUsername("mybatis user09"); //6.执行操作 int res = userDao.saveUser(user); System.out.println(res); System.out.println(user.getId()); } @Before//在测试方法执行之前执行 public void init()throws Exception { //1.读取配置文件 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建构建者对象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3.创建 SqlSession 工厂对象 factory = builder.build(in); //4.创建 SqlSession 对象 session = factory.openSession(); //5.创建 Dao 的代理对象 userDao = session.getMapper(IUserDao.class); } @After//在测试方法执行完成之后执行 public void destroy() throws Exception{ //7.提交事务 session.commit(); //8.释放资源 session.close(); in.close(); }
这是我们的 Connection 的整个变化过程,通过分析我们能够发现之前的 CUD 操作过程中,我们都要手动进 行事务的提交,原因是 setAutoCommit()方法,在执行时它的值被设置为 false 了,所以我们在 CUD 操作中, 必须通过 sqlSession.commit()方法来执行提交操作。
-
Mybatis 自动提交事务的设置
通过上面的研究和分析,现在我们一起思考,为什么 CUD 过程中必须使用 sqlSession.commit()提交事 务?主要原因就是在连接池中取出的连接,都会将调用 connection.setAutoCommit(false)方法,这样我们 就必须使用 sqlSession.commit()方法,相当于使用了 JDBC 中的 connection.commit()方法实现事务提 交。 明白这一点后,我们现在一起尝试不进行手动提交,一样实现 CUD 操作。 @Before//在测试方法执行之前执行 public void init()throws Exception { //1.读取配置文件 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建构建者对象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3.创建 SqlSession 工厂对象 factory = builder.build(in); //4.创建 SqlSession 对象 session = factory.openSession(true); //5.创建 Dao 的代理对象 userDao = session.getMapper(IUserDao.class); } @After//在测试方法执行完成之后执行 public void destroy() throws Exception{ //7.释放资源 session.close(); in.close(); }
我们发现,此时事务就设置为自动提交了,同样可以实现CUD操作时记录的保存。虽然这也是一种方式,但就 编程而言,设置为自动提交方式为 false 再根据情况决定是否进行提交,这种方式更常用。因为我们可以根据业务 情况来决定提交是否进行提交。
1.12 动态SQL
Mybatis 的映射文件中,前面我们的 SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的 SQL 是动态变 化的,此时在前面的学习中我们的 SQL 就不能满足要求了。
动态 SQL 之标签
我们根据实体类的不同取值,使用不同的 SQL 语句来进行查询。比如在 id 如果不为空时可以根据 id 查询, 如果 username 不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。
-
持久层 Dao 映射配置
<select id="findByUser" resultType="user" parameterType="user"> select * from user where 1=1 <if test="username!=null and username != '' "> and username like #{username} </if> <if test="address != null"> and address like #{address} </if> </select>
注意:标签的 test 属性中写的是对象的属性名,如果是包装类的对象要使用 OGNL 表达式的写法。 另外要注意 where 1=1 的作用!
-
测试
@Test public void testFindByUser() { User u = new User(); u.setUsername("%王%"); u.setAddress("%顺义%"); //6.执行操作 List<User> users = userDao.findByUser(u); for(User user : users) { System.out.println(user); } }
动态 SQL 之标签
为了简化上面 where 1=1 的条件拼装,我们可以采用标签来简化开发。
-
持久层 Dao 映射配置
<!-- 根据用户信息查询 --> <select id="findByUser" resultType="user" parameterType="user"> <include refid="defaultSql"></include> <where> <if test="username!=null and username != '' "> and username like #{username} </if> <if test="address != null"> and address like #{address} </if> </where> </select>
动态标签之标签
需求
传入多个 id 查询用户信息,用下边两个 sql 实现:
SELECT * FROM USERS WHERE username LIKE '%张%' AND (id =10 OR id =89 OR id=16)
SELECT * FROM USERS WHERE username LIKE '%张%' AND id IN (10,89,16)
这样我们在进行范围查询时,就要将一个集合中的值,作为参数动态添加进来。
这样我们将如何进行参数的传递?
-
在 QueryVo 中加入一个 List 集合用于封装参数
public class QueryVo implements Serializable { private List<Integer> ids; public List<Integer> getIds() { return ids; } public void setIds(List<Integer> ids) { this.ids = ids; } }
-
持久层 Dao 接口
List<User> findInIds(QueryVo vo);
-
持久层 Dao 映射配置
<!-- 查询所有用户在 id 的集合之中 --> <select id="findInIds" resultType="user" parameterType="queryvo"> <!-- select * from user where id in (1,2,3,4,5); --> <include refid="defaultSql"></include> <where> <if test="ids != null and ids.size() > 0"> <foreach collection="ids" open="id in ( " close=")" item="uid" separator=","> #{uid} </foreach> </if> </where> </select> SQL 语句: select 字段 from user where id in (?)
**
<foreach>
**标签用于遍历集合,它的属性:**
collection:
**代表要遍历的集合元素,注意编写时不要写#{}open
:代表语句的开始部分close
:代表结束部分item
:代表遍历集合的每个元素,生成的变量名separator
:代表分隔符
Mybatis 中简化编写的 SQL 片段
Sql 中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的。
-
定义代码片段
<!-- 抽取重复的语句代码片段 --> <sql id="defaultSql"> select * from user </sql>
-
引用代码片段
<!-- 配置查询所有操作 --> <select id="findAll" resultType="user"> <include refid="defaultSql"></include> </select> <!-- 根据 id 查询 --> <select id="findById" resultType="UsEr" parameterType="int"> <include refid="defaultSql"></include> where id = #{uid} </select>
1.13 Mybatis多表查询(一对一)
需求 查询所有账户信息,关联查询下单用户信息。
注意: 因为一个账户信息只能供某个用户使用,所以从查询账户信息出发关联查询用户信息为一对一查询。如 果从用户信息出发查询用户下的账户信息则为一对多查询,因为一个用户可以有多个账户。
方式一:定义一个专门的类作为输出类型
-
定义账户信息的实体类
public class Account implements Serializable { private Integer id; private Integer uid; private Double money; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Integer getUid() { return uid; } public void setUid(Integer uid) { this.uid = uid; } public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } @Override public String toString() { return "Account [id=" + id + ", uid=" + uid + ", money=" + money + "]"; } }
-
编写 Sql 语句
实现查询账户信息时,也要查询账户所对应的用户信息。 SELECT account.*, user.username, user.address FROM account, user WHERE account.uid = user.id
-
定义 AccountUser 类
为了能够封装上面 SQL 语句的查询结果,定义 AccountCustomer 类中要包含账户信息同时还要包含用户信 息,所以我们要在定义 AccountUser 类时可以继承 User 类。
public class AccountUser extends Account implements Serializable { private String username; private String address; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return super.toString() + " AccountUser [username=" + username + ", address=" + address + "]"; } }
-
定义账户的持久层 Dao 接口
public interface IAccountDao { /** * 查询所有账户,同时获取账户的所属用户名称以及它的地址信息 * @return */ List<AccountUser> findAll(); }
-
定义 AccountDao.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.IAccountDao"> <!-- 配置查询所有操作--> <select id="findAll" resultType="accountuser"> select a.*,u.username,u.address from account a,user u where a.uid =u.id; </select> </mapper>
**注意:**因为上面查询的结果中包含了账户信息同时还包含了用户信息,所以我们的返回值类型 returnType 的值设置为 AccountUser 类型,这样就可以接收账户信息和用户信息了。
-
创建 AccountTest 测试类
public class AccountTest { private InputStream in ; private SqlSessionFactory factory; private SqlSession session; private IAccountDao accountDao; @Test public void testFindAll() { //6.执行操作 List<AccountUser> accountusers = accountDao.findAll(); for(AccountUser au : accountusers) { System.out.println(au); } } @Before//在测试方法执行之前执行 public void init()throws Exception { //1.读取配置文件 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建构建者对象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3.创建 SqlSession 工厂对象 factory = builder.build(in); //4.创建 SqlSession 对象 session = factory.openSession(); //5.创建 Dao 的代理对象 accountDao = session.getMapper(IAccountDao.class); } @After//在测试方法执行完成之后执行 public void destroy() throws Exception{ session.commit(); //7.释放资源 session.close(); in.close(); } }
方式二:使用resultMap
-
修改 Account 类
在 Account 类中加入 User 类的对象作为 Account 类的一个属性。
public class Account implements Serializable { private Integer id; private Integer uid; private Double money; private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Integer getUid() { return uid; } public void setUid(Integer uid) { this.uid = uid; } public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } @Override public String toString() { return "Account [id=" + id + ", uid=" + uid + ", money=" + money + "]"; } }
-
修改 AccountDao 接口中的方法
public interface IAccountDao { /** * 查询所有账户,同时获取账户的所属用户名称以及它的地址信息 * @return */ List<Account> findAll(); }
**注意:**第二种方式,将返回值改 为了 Account 类型。 因为 Account 类中包含了一个 User 类的对象,它可以封装账户所对应的用户信息。
-
重新定义 AccountDao.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.IAccountDao"> <!-- 建立对应关系 --> <resultMap type="account" id="accountMap"> <id column="aid" property="id"/> <result column="uid" property="uid"/> <result column="money" property="money"/> <!-- 它是用于指定从表方的引用实体属性的 --> <association property="user" javaType="user"> <id column="id" property="id"/> <result column="username" property="username"/> <result column="sex" property="sex"/> <result column="birthday" property="birthday"/> <result column="address" property="address"/> </association> </resultMap> <select id="findAll" resultMap="accountMap"> select u.*,a.id as aid,a.uid,a.money from account a,user u where a.uid =u.id; </select> </mapper>
-
在 AccountTest 类中加入测试方法
@Test public void testFindAll() { List<Account> accounts = accountDao.findAll(); for(Account au : accounts) { System.out.println(au); System.out.println(au.getUser()); } }
1.14一对多查询
需求: 查询所有用户信息及用户关联的账户信息。
分析: 用户信息和他的账户信息为一对多关系,并且查询过程中如果用户没有账户信息,此时也要将用户信息 查询出来,我们想到了左外连接查询比较合适。
-
编写 SQL 语句
SELECT u.*, acc.id id, acc.uid, acc.money FROM user u LEFT JOIN account acc ON u.id = acc.uid
-
User 类加入 List
public class User implements Serializable { private Integer id; private String username; private Date birthday; private String sex; private String address; private List<Account> accounts; public List<Account> getAccounts() { return accounts; } public void setAccounts(List<Account> accounts) { this.accounts = accounts; } 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 + "]"; } }
-
用户持久层 Dao 接口中加入查询方法
List<User> findAll();
-
用户持久层 Dao 映射文件配置
<?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.IUserDao"> <resultMap type="user" id="userMap"> <id column="id" property="id"></id> <result column="username" property="username"/> <result column="address" property="address"/> <result column="sex" property="sex"/> <result column="birthday" property="birthday"/> <!-- collection 是用于建立一对多中集合属性的对应关系 ofType 用于指定集合元素的数据类型 --> <collection property="accounts" ofType="account"> <id column="aid" property="id"/> <result column="uid" property="uid"/> <result column="money" property="money"/> </collection> </resultMap> <!-- 配置查询所有操作 --> <select id="findAll" resultMap="userMap"> select u.*,a.id as aid ,a.uid,a.money from user u left outer join account a on u.id =a.uid </select> </mapper>
collection :定义了用户关联的账户信息。表示关联查询结果集
property=“accList”: 关联查询的结果集存储在 User 对象的上哪个属性。
ofType=“account”: 指定关联查询的结果集中的对象类型即List中的对象类型。此处可以使用别名,也可以使用全限定名。
-
测试方法
public class UserTest { private InputStream in ; private SqlSessionFactory factory; private SqlSession session; private IUserDao userDao; @Test public void testFindAll() { //6.执行操作 List<User> users = userDao.findAll(); for(User user : users) { System.out.println("-------每个用户的内容---------"); System.out.println(user); System.out.println(user.getAccounts()); } } @Before//在测试方法执行之前执行 public void init()throws Exception { //1.读取配置文件 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建构建者对象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3.创建 SqlSession 工厂对象 factory = builder.build(in); //4.创建 SqlSession 对象 session = factory.openSession(); //5.创建 Dao 的代理对象 userDao = session.getMapper(IUserDao.class); } @After//在测试方法执行完成之后执行 public void destroy() throws Exception{ session.commit(); //7.释放资源 session.close(); in.close(); } }
1.15 Mybatis 多表查询之多对多
实现 Role 到 User 多对多
-
业务要求及实现 SQL
需求: 实现查询所有对象并且加载它所分配的用户信息。
分析: 查询角色我们需要用到Role表,但角色分配的用户的信息我们并不能直接找到用户信息,而是要通过中 间表(USER_ROLE 表)才能关联到用户信息。
SELECT r.*,u.id uid, u.username username, u.birthday birthday, u.sex sex, u.address address FROM ROLE r INNER JOIN USER_ROLE ur ON ( r.id = ur.rid) INNER JOIN USER u ON (ur.uid = u.id);
-
编写角色实体类
public class Role implements Serializable { private Integer roleId; private String roleName; private String roleDesc; //多对多的关系映射:一个角色可以赋予多个用户 private List<User> users; public List<User> getUsers() { return users; } public void setUsers(List<User> users) { this.users = users; } public Integer getRoleId() { return roleId; } public void setRoleId(Integer roleId) { this.roleId = roleId; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public String getRoleDesc() { return roleDesc; } public void setRoleDesc(String roleDesc) { this.roleDesc = roleDesc; } @Override public String toString() { return "Role{" + "roleId=" + roleId + ", roleName='" + roleName + '\'' + ", roleDesc='" + roleDesc + '\'' + '}'; } }
-
编写 Role 持久层接口
public interface IRoleDao { /** * 查询所有角色 * @return */ List<Role> findAll(); }
-
编写映射文件
<?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.IRoleDao"> <!--定义 role 表的 ResultMap--> <resultMap id="roleMap" type="role"> <id property="roleId" column="rid"></id> <result property="roleName" column="role_name"></result> <result property="roleDesc" column="role_desc"></result> <collection property="users" ofType="user"> <id column="id" property="id"></id> <result column="username" property="username"></result> <result column="address" property="address"></result> <result column="sex" property="sex"></result> <result column="birthday" property="birthday"></result> </collection> </resultMap> <!--查询所有--> <select id="findAll" resultMap="roleMap"> select u.*,r.id as rid,r.role_name,r.role_desc from role r left outer join user_role ur on r.id = ur.rid left outer join user u on u.id = ur.uid </select> </mapper>
-
编写测试类
public class RoleTest { private InputStream in; private SqlSession sqlSession; private IRoleDao roleDao; @Before//用于在测试方法执行之前执行 public void init()throws Exception{ //1.读取配置文件,生成字节输入流 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.获取 SqlSessionFactory SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); //3.获取 SqlSession 对象 sqlSession = factory.openSession(true); //4.获取 dao 的代理对象 roleDao = sqlSession.getMapper(IRoleDao.class); } @After//用于在测试方法执行之后执行 public void destroy()throws Exception{ //提交事务 // sqlSession.commit(); //6.释放资源 sqlSession.close(); in.close(); } /** * 测试查询所有 */ @Test public void testFindAll(){ List<Role> roles = roleDao.findAll(); for(Role role : roles){ System.out.println("---每个角色的信息----"); System.out.println(role); System.out.println(role.getUsers()); } } }
1.16 Mybatis的缓存
Mybatis中的延迟加载
**问题:**在一对多中,当我们有一个用户,他有100个账户,在查询用户中,要不要把关联的账户查出来;在查询账户时,用不用把用户查出来
在查询用户时,用户下的账户信息,应该是什么时候使用,什么时候查询
在查询账户时,账户所属的用户信息应该是随着账户查询时一起查询出来
**延迟加载:**在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)
**好处:**先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速 度要快。
**坏处:**因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗 时间,所以可能造成用户等待时间变长,造成用户体验下降。
**立即加载:**不管用不用,只要一调用方法,马上发起查询
在对应的四种表关系中:一对多,多对一,一对一,多对多
一对多,多对多:通常情况下我们都是采用延迟加载
多对一,一对一:通常情况下我们都是采用立即加载
使用 assocation 实现延迟加载
-
账户的持久层 DAO 接口
public interface IAccountDao { /** * 查询所有账户,同时获取账户的所属用户名称以及它的地址信息 * @return */ List<Account> findAll(); }
-
账户的持久层映射文件
<?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.IAccountDao"> <!-- 建立对应关系 --> <resultMap type="account" id="accountMap"> <id column="aid" property="id"/> <result column="uid" property="uid"/> <result column="money" property="money"/> <!-- 它是用于指定从表方的引用实体属性的 --> <association property="user" javaType="user" select="com.itheima.dao.IUserDao.findById" column="uid"> </association> </resultMap> <select id="findAll" resultMap="accountMap"> select * from account </select> </mapper>
select: 填写我们要调用的 select 映射的 id
column : 填写我们要传递给 select 映射的参数
-
用户的持久层接口和映射文件
public interface IUserDao { /** * 根据 id 查询 * @param userId * @return */ User findById(Integer userId); } <?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.IUserDao"> <!-- 根据 id 查询 --> <select id="findById" resultType="user" parameterType="int" > select * from user where id = #{uid} </select> </mapper>
-
开启 Mybatis 的延迟加载策略
我们需要在 Mybatis 的配置文件 SqlMapConfig.xml 文件中添加延迟加载的配置。 <!-- 开启延迟加载的支持 --> <settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>
-
编写测试只查账户信息不查用户信息
public class AccountTest { private InputStream in ; private SqlSessionFactory factory; private SqlSession session; private IAccountDao accountDao; @Test public void testFindAll() { //6.执行操作 List<Account> accounts = accountDao.findAll(); } @Before//在测试方法执行之前执行 public void init()throws Exception { //1.读取配置文件 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建构建者对象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3.创建 SqlSession 工厂对象 factory = builder.build(in); //4.创建 SqlSession 对象 session = factory.openSession(); //5.创建 Dao 的代理对象 accountDao = session.getMapper(IAccountDao.class); } @After//在测试方法执行完成之后执行 public void destroy() throws Exception{ //7.释放资源 session.close(); in.close(); } }
使用 Collection 实现延迟加载
同样我们也可以在一对多关系配置的结点中配置延迟加载策略。
**<collection>
**结点中也有 select 属性,column 属性。
需求: 完成加载用户对象时,查询该用户所拥有的账户信息。
-
在 User 实体类中加入 List属性
public class User implements Serializable { private Integer id; private String username; private Date birthday; private String sex; private String address; private List<Account> accounts; public List<Account> getAccounts() { return accounts; } public void setAccounts(List<Account> accounts) { this.accounts = accounts; } 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 + "]"; } }
-
编写用户和账户持久层接口的方法
/** * 查询所有用户,同时获取出每个用户下的所有账户信息 * @return */ List<User> findAll(); /** * 根据用户 id 查询账户信息 * @param uid * @return */ List<Account> findByUid(Integer uid);
-
编写用户持久层映射配置
<resultMap type="user" id="userMap"> <id column="id" property="id"></id> <result column="username" property="username"/> <result column="address" property="address"/> <result column="sex" property="sex"/> <result column="birthday" property="birthday"/> <!-- collection 是用于建立一对多中集合属性的对应关系 ofType 用于指定集合元素的数据类型 select 是用于指定查询账户的唯一标识(账户的 dao 全限定类名加上方法名称) column 是用于指定使用哪个字段的值作为条件查询 --> <collection property="accounts" ofType="account" select="com.itheima.dao.IAccountDao.findByUid" column="id"> </collection> </resultMap> <!-- 配置查询所有操作 --> <select id="findAll" resultMap="userMap"> select * from user </select>
**
<collection>
**标签: 主要用于加载关联的集合对象select 属性: 用于指定查询 account 列表的 sql 语句,所以填写的是该 sql 映射的 id
column 属性: 用于指定 select 属性的 sql 语句的参数来源,上面的参数来自于 user 的 id 列,所以就写成 id 这一 个字段名了
-
编写账户持久层映射配置
<!-- 根据用户 id 查询账户信息 --> <select id="findByUid" resultType="account" parameterType="int"> select * from account where uid = #{uid} </select>
-
测试只加载用户信息
public class UserTest { private InputStream in ; private SqlSessionFactory factory; private SqlSession session; private IUserDao userDao; @Test public void testFindAll() { //6.执行操作 List<User> users = userDao.findAll(); } @Before//在测试方法执行之前执行 public void init()throws Exception { //1.读取配置文件 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建构建者对象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3.创建 SqlSession 工厂对象 factory = builder.build(in); //4.创建 SqlSession 对象 session = factory.openSession(); //5.创建 Dao 的代理对象 userDao = session.getMapper(IUserDao.class); } @After//在测试方法执行完成之后执行 public void destroy() throws Exception{ session.commit(); //7.释放资源 session.close(); in.close(); } }
缓存的概念
**缓存:**存在内存里的临时数据
**为什么使用缓存:**减少和数据库的交互次数,提高执行效率
**适用于缓存:**经常查询,并且不经常改变的,数据的正确与否对最终结果影响不大的
**不适用于缓存:**经常改变的数据,或数据的正确与否对最终结果影响很大的,例如:商品的库存、银行的汇率、股市的牌价
Mybatis中的一级缓存与二级缓存
**一级缓存:**它指的是mybatis中SqlSession对象的缓存。当我们执行查询后,查询的结果会同时存入到SqlSession为我们提供的一块区域中,该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlSession中查询是否有,有的话直接拿出来用。当SqlSession对象消失时,mybatis的一级缓存也就消失了
SqlSession.clearCache()
:可以清空缓存
**二级缓存:**它指的是Mubatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
二级缓存中存放的是数据而不是对象
二级缓存的使用步骤:
- 让MyBatis框架支持二级缓存(在
SqlMapConfig.xml
中配置)(在<settings>
标签中增加<setting name="cacheEnable" value="true"/>
标签,默认值为true - 让当前的映射文件支持二级缓存(在
IUserDao.xml
中配置)(增加标签<cache/>
) - 让当前的操作支持二级缓存(在
select
标签中配置)(在对应操作的标签属性中添加xxxCache="true"
)
1.17 MyBatis注解开发
这几年来注解开发越来越流行,Mybatis 也可以使用注解开发方式,这样我们就可以减少编写 Mapper 映射 文件了。本次我们先围绕一些基本的 CRUD 来学习,再学习复杂映射关系及延迟加载。
mybatis 的常用注解说明
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider: 实现动态 SQL 映射
@CacheNamespace:实现注解二级缓存的使用
使用 Mybatis 注解实现基本 CRUD
单表的 CRUD 操作是最基本的操作,前面我们的学习都是基于 Mybaits 的映射文件来实现的。
-
编写实体类
public class User implements Serializable { 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 "User [userId=" + userId + ", userName=" + userName + ", userBirthday=" + userBirthday + ", userSex=" + userSex + ", userAddress=" + userAddress + "]"; } }
注意: 此处我们故意和数据库表的列名不一致。
-
使用注解方式开发持久层接口
public interface IUserDao { /** * 查询所有用户 * @return */ @Select("select * from user") @Results(id="userMap", value= { @Result(id=true,column="id",property="userId"), @Result(column="username",property="userName"), @Result(column="sex",property="userSex"), @Result(column="address",property="userAddress"), @Result(column="birthday",property="userBirthday") }) List<User> findAll(); /** * 根据 id 查询一个用户 * @param userId * @return */ @Select("select * from user where id = #{uid} ") @ResultMap("userMap") User findById(Integer userId); /** * 保存操作 * @param user * @return */ @Insert("insert into user(username,sex,birthday,address)values(#{username},#{sex},#{birthday},#{address} )") @SelectKey(keyColumn="id",keyProperty="id",resultType=Integer.class,before = false, statement = { "select last_insert_id()" }) int saveUser(User user); /** * 更新操作 * @param user * @return */ @Update("update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id =#{id} ") int updateUser(User user); /** * 删除用户 * @param userId * @return */ @Delete("delete from user where id = #{uid} ") int deleteUser(Integer userId); /** * 查询使用聚合函数 * @return */ @Select("select count(*) from user ") int findTotal(); /** * 模糊查询 * @param name * @return */ @Select("select * from user where username like #{username} ") List<User> findByName(String name); }
通过注解方式,我们就不需要再去编写 UserDao.xml 映射文件了。
-
编写 SqlMapConfig 配置文件
<?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 文件的位置 --> <properties resource="jdbcConfig.properties"></properties> <!-- 配置别名的注册 --> <typeAliases> <package name="com.itheima.domain"/> </typeAliases> <!-- 配置环境 --> <environments default="mysql"> <!-- 配置 mysql 的环境 --> <environment id="mysql"> <!-- 配置事务的类型是 JDBC --> <transactionManager type="JDBC"></transactionManager> <!-- 配置数据源 --> <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> <!-- 配置 dao 接口的位置,它有两种方式 第一种:使用 mapper 标签配置 class 属性 第二种:使用 package 标签,直接指定 dao 接口所在的包 --> <package name="com.itheima.dao"/> </mappers> </configuration>
-
编写测试方法
public class MybatisAnnotationCRUDTest { /** * 测试查询所有 */ @Test public void testFindAll() { List<User> users = userDao.findAll(); for(User user : users) { System.out.println(user); } } /** * 测试查询一个 */ @Test public void testFindById() { User user = userDao.findById(41); System.out.println(user); } /** * 测试保存 */ @Test public void testSave() { User user = new User(); user.setUserName("mybatis annotation"); user.setUserSex("男"); user.setUserAddress("北京市顺义区"); user.setUserBirthday(new Date()); int res = userDao.saveUser(user); System.out.println("影响数据库记录的行数:"+res); System.out.println("插入的主键值:"+user.getUserId()); } /** * 测试更新 */ @Test public void testUpdate() { User user = userDao.findById(63); user.setUserBirthday(new Date()); user.setUserSex("女"); int res = userDao.updateUser(user); System.out.println(res); } /** * 测试删除 */ @Test public void testDelete() { int res = userDao.deleteUser(63); System.out.println(res); } /** * 测试查询使用聚合函数 */ @Test public void testFindTotal() { int res = userDao.findTotal(); System.out.println(res); } /** * 测试模糊查询 */ @Test public void testFindByName() { List<User> users = userDao.findByName("%m%"); for(User user : users) { System.out.println(user); } } private InputStream in; private SqlSessionFactory factory; private SqlSession session; private IUserDao userDao; @Before//junit 的注解 public void init()throws Exception{ //1.读取配置文件 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); factory = builder.build(in); //3.创建 session session = factory.openSession(); //4.创建代理对象 userDao = session.getMapper(IUserDao.class); } @After//junit 的注解 public void destroy()throws Exception { //提交事务 session.commit(); //释放资源 session.close(); //关闭流 in.close(); } }
使用注解实现复杂关系映射开发
实现复杂关系映射之前我们可以在映射文件中通过配置来实现,在使用注解开发时我们需要借 助@Results 注解,@Result 注解,@One 注解,@Many 注解。
-
复杂关系映射的注解说明
@Results 注解 代替的是标签<resultMap> 该注解中可以使用单个@Result 注解,也可以使用@Result 集合 @Results({@Result(),@Result()})或@Results(@Result()) @Resutl 注解 代替了 <id>标签和<result>标签 @Result 中 属性介绍: id 是否是主键字段 column 数据库的列名 property 需要装配的属性名 one 需要使用的@One 注解(@Result(one=@One)())) many 需要使用的@Many 注解(@Result(many=@many)())) @One 注解(一对一) 代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。 @One 注解属性介绍: select 指定用来多表查询的 sqlmapper fetchType 会覆盖全局的配置参数 lazyLoadingEnabled。。 使用格式: @Result(column=" ",property="",one=@One(select="")) @Many 注解(多对一) 代替了<Collection>标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。 注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType (一般为 ArrayList)但是注解中可以不定义; 使用格式: @Result(property="",column="",many=@Many(select=""))
-
使用注解实现一对一复杂关系映射及延迟加载
需求: 加载账户信息时并且加载该账户的用户信息,根据情况可实现延迟加载。(注解方式实现)
-
添加 User 实体类及 Account 实体类
public class User implements Serializable { 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 "User [userId=" + userId + ", userName=" + userName + ", userBirthday=" + userBirthday + ", userSex=" + userSex + ", userAddress=" + userAddress + "]"; } } /** * * <p>Title: Account</p> * <p>Description: 账户的实体类</p> * <p>Company: http://www.itheima.com/ </p> */ public class Account implements Serializable { private Integer id; private Integer uid; private Double money; //多对一关系映射:从表方应该包含一个主表方的对象引用 private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Integer getUid() { return uid; } public void setUid(Integer uid) { this.uid = uid; } public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } @Override public String toString() { return "Account [id=" + id + ", uid=" + uid + ", money=" + money + "]"; } }
-
添加账户的持久层接口并使用注解配置
public interface IAccountDao { /** * 查询所有账户,采用延迟加载的方式查询账户的所属用户 * @return */ @Select("select * from account") @Results(id="accountMap", value= { @Result(id=true,column="id",property="id"), @Result(column="uid",property="uid"), @Result(column="money",property="money"), @Result(column="uid", property="user", one=@One(select="com.itheima.dao.IUserDao.findById", fetchType=FetchType.LAZY) ) }) List<Account> findAll(); }
-
添加用户的持久层接口并使用注解配置
public interface IUserDao { /** * 查询所有用户 * @return */ @Select("select * from user") @Results(id="userMap", value= { @Result(id=true,column="id",property="userId"), @Result(column="username",property="userName"), @Result(column="sex",property="userSex"), @Result(column="address",property="userAddress"), @Result(column="birthday",property="userBirthday") }) List<User> findAll(); /** * 根据 id 查询一个用户 * @param userId * @return */ @Select("select * from user where id = #{uid} ") @ResultMap("userMap") User findById(Integer userId); }
-
测试一对一关联及延迟加载
public class AccountTest { @Test public void testFindAll() { List<Account> accounts = accountDao.findAll(); // for(Account account : accounts) { // System.out.println(account); // System.out.println(account.getUser()); // } }
使用注解实现一对多复杂关系映射
需求: 查询用户信息时,也要查询他的账户列表。使用注解方式实现。
分析: 一个用户具有多个账户信息,所以形成了用户(User)与账户(Account)之间的一对多关系。
-
User 实体类加入 List
public class User implements Serializable { private Integer userId; private String userName; private Date userBirthday; private String userSex; private String userAddress; //一对多关系映射:主表方法应该包含一个从表方的集合引用 private List<Account> accounts; public List<Account> getAccounts() { return accounts; } public void setAccounts(List<Account> accounts) { this.accounts = accounts; } 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 "User [userId=" + userId + ", userName=" + userName + ", userBirthday=" + userBirthday + ", userSex=" + userSex + ", userAddress=" + userAddress + "]"; } }
-
编写用户的持久层接口并使用注解配置
public interface IUserDao { /** * 查询所有用户 * @return */ @Select("select * from user") @Results(id="userMap", value= { @Result(id=true,column="id",property="userId"), @Result(column="username",property="userName"), @Result(column="sex",property="userSex"), @Result(column="address",property="userAddress"), @Result(column="birthday",property="userBirthday"), @Result(column="id",property="accounts", many=@Many( select="com.itheima.dao.IAccountDao.findByUid", fetchType=FetchType.LAZY ) ) }) List<User> findAll(); }
@Many: 相当于的配置
select 属性:代表将要执行的 sql 语句
fetchType 属性:代表加载方式,一般如果要延迟加载都设置为 LAZY 的值
-
编写账户的持久层接口并使用注解配置
public interface IAccountDao { /** * 根据用户 id 查询用户下的所有账户 * @param userId * @return */ @Select("select * from account where uid = #{uid} ") List<Account> findByUid(Integer userId); }
-
添加测试方法
public class UserTest { /** * 测试查询所有 */ @Test public void testFindAll() { List<User> users = userDao.findAll(); // for(User user : users) { // System.out.println("-----每个用户的内容-----"); // System.out.println(user); // System.out.println(user.getAccounts()); // } } private InputStream in; private SqlSessionFactory factory; private SqlSession session; private IUserDao userDao; @Before//junit 的注解 public void init()throws Exception{ //1.读取配置文件 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); factory = builder.build(in); //3.创建 session session = factory.openSession(); //4.创建代理对象 userDao = session.getMapper(IUserDao.class); } @After//junit 的注解 public void destroy()throws Exception { //提交事务 session.commit(); //释放资源 session.close(); //关闭流 in.close(); } }
1.18 mybatis 基于注解的二级缓存
-
在 SqlMapConfig 中开启二级缓存支持
<!-- 配置二级缓存 --> <settings> <!-- 开启二级缓存的支持 --> <setting name="cacheEnabled" value="true"/> </settings>
-
在持久层接口中使用注解配置二级缓存
@CacheNamespace(blocking=true)//mybatis 基于注解方式实现配置二级缓存 public interface IUserDao {}