Mybatis 框架课程第一天
第1章 框架概述
1.1.1 什么是框架
框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。前者是从应用方面而后者是从目的方面给出的定义。简而言之,框架其实就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。简单说就是使用别人搭好的舞台,你来做表演。而且,框架一般是成熟的,不断升级的软件。
1.1.2 框架要解决的问题
框架要解决的最重要的一个问题是技术整合的问题,在 J2EE 的 框架中,有着各种各样的技术,不同的软件企业需要从 J2EE 中选择不同的技术,这就使得软件企业最终的应用依赖于这些技术,技术自身的复杂性和技术的风险性将会直接对应用造成冲击。而应用是软件企业的核心,是竞争力的关键所在,因此应该将应用自身的设计和具体的实现技术解耦。这样,软件企业的研发将集中在应用的设计上,而不是具体的技术实现,技术实现是应用的底层支撑,它不应该直接对应用产生影响。
框架一般处在低层应用平台(如 J2EE)和高层业务逻辑之间的中间层。
1.1.3 软件开发的分层重要性
框架的重要性在于它实现了部分功能,并且能够很好的将低层应用平台和高层业务逻辑进行了缓和。为了实现软件工程中的“高内聚、低耦合”。把问题划分开来各个解决,易于控制,易于延展,易于分配资源。我们常见的MVC 软件设计思想就是很好的分层思想。
1.1.4 分层开发下的常见框架
常见的java框架:
1.解决数据持久化问题的框架
作为持久层的框架,还有一个封装程度更高的框架就是Hibernate,但这个框架因为各种原因目前在国内的
流行程度下降太多,现在公司开发也越来越少使用。目前使用 Spring Data 来实现数据持久化也是一种趋势。
2.解决 WEB 层问题的 MVC 框架
3.解决技术整合问题的框架
1.1.5 MyBatis 框架概述
mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。mybatis 通过 xml 或注解的方式将要执行的各statement 配置起来,并通过 java 对象和 statement 中sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。
采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了jdbc api 底层访问细节,使我们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。为了我们能够更好掌握框架运行的内部过程,并且有更好的体验,下面我们将从自定义 Mybatis 框架开始来学习框架。此时我们将会体验框架从无到有的过程体验,也能够很好的综合前面阶段所学的基础。
1.2 JDBC 编程的分析
1.2.1 jdbc 程序的回顾
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//通过驱动管理类获取数据库链接
connection = DriverManager
.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8","root", "123456");
//定义 sql 语句 ?表示占位符
String sql = "select * from user where username = ?";
//获取预处理 statement
preparedStatement = connection.prepareStatement(sql);
//设置参数,第一个参数为 sql 语句中参数的序号(从 1 开始),第二个参数为设置的参数值
preparedStatement.setString(1, "王五");
//向数据库发出 sql 执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
//遍历查询结果集
while(resultSet.next()){
System.out.println(resultSet.getString("id")+"
"+resultSet.getString("username"));
}
} catch (Exception e) {
e.printStackTrace();
}finally{
//释放资源
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//上边使用 jdbc 的原始方法(未经封装)实现了查询数据库表记录的操作。
1.2.2 jdbc 问题分析
1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
2、Sql 语句在代码中硬编码,造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变 java代码。
3、使用 preparedStatement 向占有位符号传参数存在硬编码,因为 sql 语句的 where 条件不一定,可能多也可能少,修改 sql 还要修改代码,系统不易维护。
4、对结果集解析存在硬编码(查询列名),sql 变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成 pojo 对象解析比较方便。
Mybatis配置文件的约束:
Mapper的约束.xml:这个是接口的
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
Config的约束.xml:这个是配置文件的
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
第2章 Mybatis 框架快速入门
2.1.1 官网下载 Mybatis 框架
从百度中“mybatis download”可以下载最新的 Mybatis 开发包。
下载相关的 jar 包
或 maven 开发的坐标。
2.2 搭建 Mybatis 开发环境
2.2.1 创建 maven 工程
创建 mybatis01 的工程,工程信息如下:
Groupid:com.itmei
ArtifactId:mybatis01
Packing:jar
2.2.2 添加 Mybatis3.4.5 的坐标
在 pom.xml 文件中添加 Mybatis3.4.5 的坐标,如下:
<dependencies>
<!-- mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.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>
2.2.3 编写USer实体类
实体类的名称和数据库的名称一样
注意:所有的实体类都要实现Serializable(序列化接口)
package com.itmei.domain;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
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 + '\'' +
'}';
}
}
2.2.4 编写持久层接口 IUserDao
IUserDao 接口就是我们的持久层接口(也可以写成 UserDao 或者 UserMapper),具体代码如下:
package com.itmei.dao;
import com.itmei.domain.User;
import java.util.List;
/**
* 用户的持久层接口
*/
public interface IUSerDao {
/**
* 查询所有操作
* @return
*/
List<User> findAll();
}
2.2.6 编写 SqlMapConfig.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--mybatis的主配置文件-->
<configuration>
<!-- 配置环境-->
<environments default="mysql">
<!-- 配置mysql的环境-->
<environment id="mysql">
<!-- 配置事务的类型-->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源(连接池 )-->
<dataSource type="POOLED">
<!-- 配置连接数据库的4个基本信息 -->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 指定映射位置的位置,映射配置文件指定的一个dao独立的配置文件-->
<mappers>
<mapper resource="com/itmei/dao/IUserDao.xml"/>
</mappers>
</configuration>
2.2.7 编写测试类
package com.itmei.test;
import com.itmei.dao.IUSerDao;
import com.itmei.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* mybatis入门案例
*/
public class MybatisTest {
/**
* 入门案例
* @param args
*/
public static void main(String[] args) throws IOException {
//1.读取配置文件
InputStream in= Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建sqlSessionFactory工厂
SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
SqlSessionFactory factory=builder.build(in);
//3.使用工厂生产一个SqlSession对象
SqlSession session=factory.openSession();
//4.使用SqlSession创建Dao的代理对象
IUSerDao userDao=session.getMapper(IUSerDao.class);
//5.使用代理执行方法
List<User> users=userDao.findAll();
for (User user: users) {
System.out.println(user);
}
//6.释放资源
session.close();
in.close();
}
}
2.3 小结
通过快速入门示例,我们发现使用 mybatis 是非常容易的一件事情,因为只需要编写 Dao 接口并且按照mybatis 要求编写两个配置文件,就可以实现功能。远比我们之前的 jdbc 方便多了。(我们使用注解之后,将变得更为简单,只需要编写一个 mybatis 配置文件就够了。)
但是,这里面包含了许多细节,比如为什么会有工厂对象(SqlSessionFactory),为什么有了工厂之后还要有构建者对象(SqlSessionFactoryBuilder),为什么 IUserDao.xml 在创建时有位置和文件名的要求等等。
这些问题我们在自定义 mybatis 框架的章节,通过层层剥离的方式,给大家讲解。
请注意:我们讲解自定义 Mybatis 框架,不是让大家回去自己去写个 mybatis,而是让我们能更好了了解mybatis 内部是怎么执行的,在以后的开发中能更好的使用 mybatis 框架,同时对它的设计理念(设计模式)有
一个认识.
2.4 补充(基于注解的 mybatis 使用)
2.4.1 在持久层接口中添加注解
package com.itmei.dao;
import com.itmei.domain.User;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* 用户的持久层接口
*/
public interface IUSerDao {
/**
* 查询所有操作
* @return
*/
@Select("select * from user")
List<User> findAll();
}
2.4.2 修改 SqlMapConfig.xml
<!-- 告知 mybatis 映射配置的位置 -->
<mappers>
<mapper class="com.itheima.dao.IUserDao"/>
</mappers>
2.4.3 注意事项:
在使用基于注解的 Mybatis 配置时,请移除 xml 的映射配置(IUserDao.xml)。因为mybatis中只能使用其中之一,要么注解开发,要么使用XML开发。
补充
Mybatis 框架课程第二天
第1章 回顾
1.1 自定义流程再分析
1.2 mybatis 环境搭建步骤
第一步:创建 maven 工程
第二步:导入坐标
第三步:编写必要代码(实体类和持久层接口)
第四步:编写 SqlMapConfig.xml(配置文件)
第五步:编写映射配置文件
第六步:编写测试类
第2章 基于代理 Dao 实现 CRUD 操作
使用要求:
1、持久层接口和持久层接口的映射配置必须在相同的包下
2、持久层映射配置中 mapper 标签的 namespace 属性取值必须是持久层接口的全限定类名一样
3、SQL 语句的配置标签 select,insert,delete,update的 id 属性必须和持久层接口的方法名相同。
2.1 根据 ID 查询
2.1.1 在持久层接口中添加 findById 方法
/**
* 根据id查询用户信息
* @param userId
* @return
*/
User findById(Integer userId);
2.1.2 在用户的映射配置文件中配置
<!--根据id查询用户-->
<select id="findById" parameterType="INT" resultType="com.itmei.domain.User">
select * from user where id=#{uid};
</select>
细节:
resultType 属性:
用于指定结果集的类型。
parameterType 属性:
用于指定传入参数的类型。
sql 语句中使用#{}字符:
它代表占位符,相当于原来 jdbc 部分所学的?,都是用于执行语句时替换实际的数据。
具体的数据是由#{}里面的内容决定的。
#{}中内容的写法:
由于数据类型是基本类型,所以此处可以随意写。
2.1.3 在测试类添加测试
public class MybatisTest {
private InputStream in;
private IUserDao userDao;
@Before//用于测试方法执行之前执行
public void init() throws Exception {
//1.读取配置,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.使用工厂对象,创建daoduix
userDao=new UserDaoImpl(factory);
}
@After//用于在测试方法执行之后执行
public void destroy() throws Exception {
//6.释放资源
in.close();
}
/**
* 通过id查询用户操作
*/
@Test
public void testFindOne(){
//5.执行id查询用户方法
User user=userDao.findById(51);
System.out.println(user);
}
}
2.2 保存操作
2.2.1 在持久层接口中添加新增方法
/**
* 保存用户
* @param user
* @return 影响数据库记录的行数
*/
int saveUser(User user);
2.2.2 在用户的映射配置文件中配置
<!-- 保存用户-->
<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。
2.2.3 添加测试类中的测试方法
@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();
}
2.2.4 问题扩展:新增用户 id 的返回值
2.3 用户更新
2.3.1 在持久层接口中添加更新方法
/**
* 更新用户
* @param user
*/
void updateUser(User user);
2.3.2 在用户的映射配置文件中配置
<!--更新用户-->
<update id="updateUser" parameterType="com.itmei.domain.User">
update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id};
</update>
2.3.3 加入更新的测试方法
/**
* 测试更新操作
*/
@Test
public void testUpdate(){
User user=new User();
user.setId(50);
user.setUsername("mjw");
user.setAddress("北京朝阳区");
user.setSex("男");
user.setBirthday(new Date());
//5.执行用户更新方法
userDao.updateUser(user);
}
2.4 用户删除
2.4.1 在持久层接口中添加删除方法
/**
* 根据id删除用户
* @param userID
*/
void deleteUser(Integer userID);
2.4.2 在用户的映射配置文件中配置
<!--删除用户-->
<!--如果接口的方法传递只有一个值我们可以随便取一个占位符 可以是id,uid,等-->
<delete id="deleteUser" parameterType="Integer">
delete from user where id=#{id}
</delete>
2.4.3 加入删除的测试方法
/**
* 测试删除操作
*/
@Test
public void testDelete(){
//5.执行用户删除方法
userDao.deleteUser(54);
//调用方法查询
testFindAll();
}
2.5 用户模糊查询
2.5.1 在持久层接口中添加模糊查询方法
/**
* 根据名称模糊查询用户信息
* @param username
* @return
*/
List<User> findByName(String username);
2.5.2 在用户的映射配置文件中配置
<!--根据名称模糊查询用户信息-->
<select id="findByName" parameterType="String" resultType="com.itmei.domain.User">
select * from user where username like #{usernamae}
<!-- select * from user where username like '%${value}%'; 了解-->
</select>
2.5.3 加入模糊查询的测试方法
/**
* 通过名称模糊查询用户操作
*/
@Test
public void testFindByName(){
//5.执行id查询用户方法
List<User> users=userDao.findByName("%M%");
// List<User> users=userDao.findByName("M");
for (User user:users) {
System.out.println(user);
}
}
2.5.4 模糊查询的另一种配置方式
2.5.5 #{}与${}的区别
#{}表示一个占位符号
通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换,#{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类型值,#{}括号中可以是 value 或其它名称。
${}表示拼接 sql 串
通过 ${}可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换, $ {}可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,${}括号中只能是 value。
2.5.6 模糊查询的${value}源码分析
我们一起来看 TextSqlNode 类的源码:
这就说明了源码中指定了读取的 key 的名字就是”value”,所以我们在绑定参数时就只能叫 value 的名字
了。
2.6 查询使用聚合函数count
2.6.1 在持久层接口中添加模糊查询方法
/**
* 查询用户数
* @return
*/
int findTotal();
2.6.2 在用户的映射配置文件中配置
<!--查询用户的总条数-->
<select id="findTotal" resultType="INT">
select count(id) from user ;
</select>
2.6.3 加入聚合查询的测试方法
/**
* 测试查询总记录数
*/
@Test
public void testFindTotal() {
//5.执行方法
int total = userDao.findTotal();
System.out.println("总共有:"+total+"条");
}
2.7 Mybatis 与 JDBC 编程的比较
1.数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
解决:
在 SqlMapConfig.xml 中配置数据链接池,使用连接池管理数据库链接。
2.Sql 语句写在代码中造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变 java 代码。
解决:
将 Sql 语句配置在 XXXXmapper.xml 文件中与 java 代码分离。
3.向 sql 语句传参数麻烦,因为 sql 语句的 where 条件不一定,可能多也可能少,占位符需要和参数对应。
解决:
Mybatis 自动将 java 对象映射至 sql 语句,通过 statement (声明)中的 parameterType 定义输入参数的 类型。
4.对结果集解析麻烦,sql 变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成 pojo 对 象解析比较方便。
解决:
Mybatis 自动将 sql 执行结果映射至 java 对象,通过 statement (声明)中的 resultType 定义输出结果的 类型。
第3章 Mybatis 的参数深入
3.1 parameterType 配置参数
3.1.1 使用说明
我们在上一章节中已经介绍了 SQL 语句传参,使用标签的 parameterType 属性来设定。该属性的取值可以是基本类型,引用类型(例如:String 类型),还可以是实体类类型(POJO 类)。同时也可以使用实体类的包装类,本章节将介绍如何使用实体类的包装类作为参数传递。
3.1.2 注意事项
基 本 类 型 和 String 我 们 可 以 直 接 写 类 型 名 称 , 也 可 以 使 用 包 名 . 类 名 的 方 式 , 例 如 :java.lang.String。
实体类类型,目前我们只能使用全限定类名。
究其原因,是 mybaits 在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的是实体类并没有注册别名,所以必须写全限定类名。在今天课程的最后一个章节中将讲解如何注册实体类的别名。
在 mybatis 的官方文档的说明(第 19 页)
这些都是支持的默认别名。我们也可以从源码角度来看它们分别都是如何定义出来的。
可以参考 TypeAliasRegistery.class 的源码。
3.2 传递 pojo 包装对象
开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。
Pojo 类中包含 pojo。
需求:根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中。
3.2.1 编写 QueryVo
package com.itmei.domain;
/**
* 查询条件对象
*/
public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
3.2.2 编写持久层接口
**
* 用户的持久层接口
*/
public interface IUserDao {
/**
* 根据QueryVo中的条件查询用户
* @param vo
* @return
*/
List<User> findUserByVo(QueryVo vo);
}
3.2.3 持久层接口的映射文件
<!--根据用户名称模糊查询,参数变成一个 QueryVo 对象了-->
<select id="findUserByVo" parameterType="com.itmei.domain.QueryVo"
resultType="com.itmei.domain.User">
select * from user where username like #{user.username};
</select>
3.2.4 测试包装类作为参数
/**
* 测试使用QueryVo作为查询条件
*/
@Test
public void testFindByVo(){
QueryVo vo=new QueryVo();
User user=new User();
user.setUsername("%M%");
vo.setUser(user);
//5.执行id查询用户方法
List<User> users=userDao.findUserByVo(vo);
// List<User> users=userDao.findByName("M");
for (User u:users) {
System.out.println(u);
}
}
~~
第4章 Mybatis 的输出结果封装
4.1 resultType 配置结果类型
resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型。
我们在前面的 CRUD 案例中已经对此属性进行过应用了。
需要注意的是,它和 parameterType 一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。例如:我们的实体类此时必须是全限定类名(今天最后一个章节会讲解如何配置实体类的别名)
同时,当是实体类名称是,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装。
4.1.1 基本类型示例
4.1.1.1 Dao 接口
/**
* 查询总记录数
* @return
*/
int findTotal();
4.1.1.2 映射配置
<!--查询用户的总条数-->
<select id="findTotal" resultType="INT">
select count(*) from user ;
</select>
4.1.2 实体类类型示例
/**
* 查询所有用户
* @return
*/
List<User> findAll();
4.1.2.2 映射配置
<!-- 配置查询所有操作 -->
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
</select>
4.1.3 特殊情况 (数据库名称和类名称不同)
4.1.3.1 修改实体类
注意:实体类代码如下:(此时的实体类属性和数据库表的列名已经不一致了)
/**
*
* <p>Title: User</p>
* <p>Description: 用户的实体类</p>
* <p>Company: http://www.itheima.com/ </p>
*/
package com.itmei.domain;
import java.io.Serializable;
import java.util.Date;
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 + '\'' +
'}';
}
}
4.1.3.2 Dao 接口
/**
* 查询所有用户
* @return
*/
List<User> findAll();
4.1.3.3 映射配置
<!-- 配置查询所有操作 -->
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
</select>
4.1.3.4 测试查询结果
@Test
public void testFindAll() {
List<User> users = userDao.findAll();
for(User user : users) {
System.out.println(user);
}
}
为什么名称会有值呢?
因为:mysql 在 windows 系统中不区分大小写!
4.1.3.5 修改映射配置
使用别名查询
<!-- 配置查询所有操作 -->
<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>
运行结果:
思考:
如果我们的查询很多,都使用别名的话写起来岂不是很麻烦,有没有别的解决办法呢?
请看下一小节。
4.2 resultMap 结果类型
resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。
在 select 标签中使用 resultMap 属性指定引用即可。同时 resultMap 可以实现将查询结果映射为复杂类
型的 pojo,比如在查询结果映射对象中包括 pojo 和 list 实现一对一查询和一对多查询。
4.2.1 定义 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 属性:用于指定实体类属性名称
4.2.2 映射配置
那么你只要修改select标签里面把 resultType属性替换成 resultMap=”你取的id名称”这样就可以封装成功!
<!-- 配置查询所有操作 -->
<select id="findAll" resultMap="userMap">
select * from user
</select>
4.2.3 测试结果
@Test
public void testFindAll() {
List<User> users = userDao.findAll();
for(User user : users) {
System.out.println(user);
}
}
运行结果:
第5章 Mybatis 传统 DAO 层开发[了解]
使用 Mybatis 开发 Dao,通常有两个方法,即原始 Dao 开发方式和 Mapper 接口代理开发方式。而现在主流的开发方式是接口代理开发方式,这种方式总体上更加简便。我们的课程讲解也主要以接口代理开发方式为主。在第二章节已经给大家介绍了基于代理方式的 dao 开发,现在给大家介绍一下基于传统编写 Dao 实现类的开发方式。
5.1 Mybatis 实现 DAO 的传统开发方式
5.1.1 持久层 Dao 接口
package com.itheima.dao;
import com.itheima.domain.User;
import java.util.List;
/**
* @author 黑马程序员
* @Company http://www.ithiema.com
*
* 用户的持久层接口
*/
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
List<User> findAll();
/**
* 保存用户
* @param user
*/
void saveUser(User user);
/**
* 更新用户
* @param user
*/
void updateUser(User user);
/**
* 根据Id删除用户
* @param userId
*/
void deleteUser(Integer userId);
/**
* 根据id查询用户信息
* @param userId
* @return
*/
User findById(Integer userId);
/**
* 根据名称模糊查询用户信息
* @param username
* @return
*/
List<User> findByName(String username);
/**
* 查询总用户数
* @return
*/
int findTotal();
}
5.1.2 持久层 Dao 实现类
package com.itheima.dao.impl;
import com.itheima.dao.IUserDao;
import com.itheima.domain.User;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import java.util.List;
/**
* @author 黑马程序员
* @Company http://www.ithiema.com
*/
public class UserDaoImpl implements IUserDao {
private SqlSessionFactory factory;
public UserDaoImpl(SqlSessionFactory factory){
this.factory = factory;
}
@Override
public List<User> findAll() {
//1.根据factory获取SqlSession对象
SqlSession session = factory.openSession();
//2.调用SqlSession中的方法,实现查询列表
List<User> users = session.selectList("com.itheima.dao.IUserDao.findAll");//参数就是能获取配置信息的key
//3.释放资源
session.close();
return users;
}
@Override
public void saveUser(User user) {
//1.根据factory获取SqlSession对象
SqlSession session = factory.openSession();
//2.调用方法实现保存
session.insert("com.itheima.dao.IUserDao.saveUser",user);
//3.提交事务
session.commit();
//4.释放资源
session.close();
}
@Override
public void updateUser(User user) {
//1.根据factory获取SqlSession对象
SqlSession session = factory.openSession();
//2.调用方法实现更新
session.update("com.itheima.dao.IUserDao.updateUser",user);
//3.提交事务
session.commit();
//4.释放资源
session.close();
}
@Override
public void deleteUser(Integer userId) {
//1.根据factory获取SqlSession对象
SqlSession session = factory.openSession();
//2.调用方法实现更新
session.update("com.itheima.dao.IUserDao.deleteUser",userId);
//3.提交事务
session.commit();
//4.释放资源
session.close();
}
@Override
public User findById(Integer userId) {
//1.根据factory获取SqlSession对象
SqlSession session = factory.openSession();
//2.调用SqlSession中的方法,实现查询一个
User user = session.selectOne("com.itheima.dao.IUserDao.findById",userId);
//3.释放资源
session.close();
return user;
}
@Override
public List<User> findByName(String username) {
//1.根据factory获取SqlSession对象
SqlSession session = factory.openSession();
//2.调用SqlSession中的方法,实现查询列表
List<User> users = session.selectList("com.itheima.dao.IUserDao.findByName",username);
//3.释放资源
session.close();
return users;
}
@Override
public int findTotal() {
//1.根据factory获取SqlSession对象
SqlSession session = factory.openSession();
//2.调用SqlSession中的方法,实现查询一个
Integer count = session.selectOne("com.itheima.dao.IUserDao.findTotal");
//3.释放资源
session.close();
return count;
}
}
5.1.3 持久层映射配置
<?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">
<!-- 查询所有 -->
<select id="findAll" resultType="com.itheima.domain.User">
select * from user;
</select>
<!-- 保存用户
ognl 表达式:它是 apache 提供的一种表达式语言,在 struts2 中也有应用。
Object Graphic Navigation Language 对象图导航语言
它是按照一定的语法格式来获取数据的。
语法格式就是使用 #{对象.对象}的方式
#{user.username}它会先去找 user 对象,然后在 user 对象中找到 username 属性,并把值取出来
-->
<!-- 保存用户 -->
<insert id="saveUser" parameterType="com.itheima.domain.User">
<!-- 配置插入操作后,获取插入数据的id -->
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
</insert>
<!-- 更新用户 -->
<update id="updateUser" parameterType="com.itheima.domain.User">
update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}
</update>
<!-- 删除用户-->
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id = #{uid}
</delete>
<!-- 根据id查询用户 -->
<select id="findById" parameterType="INT" resultType="com.itheima.domain.User">
select * from user where id = #{uid}
</select>
<!-- 根据名称模糊查询 -->
<select id="findByName" parameterType="string" resultType="com.itheima.domain.User">
select * from user where username like #{name}
</select>
<!-- 获取用户的总记录条数 -->
<select id="findTotal" resultType="int">
select count(id) from user;
</select>
</mapper>
5.1.4 测试类
package com.itheima.test;
import com.itheima.dao.IUserDao;
import com.itheima.dao.impl.UserDaoImpl;
import com.itheima.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
/**
*
*
* 测试mybatis的crud操作
*/
public class MybatisTest {
private InputStream in;
private IUserDao userDao;
@Before//用于在测试方法执行之前执行
public void init()throws Exception{
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.使用工厂对象,创建dao对象
userDao = new UserDaoImpl(factory);
}
@After//用于在测试方法执行之后执行
public void destroy()throws Exception{
//6.释放资源
in.close();
}
/**
* 测试查询所有
*/
@Test
public void testFindAll(){
//5.执行查询所有方法
List<User> users = userDao.findAll();
for(User user : users){
System.out.println(user);
}
}
/**
* 测试保存操作
*/
@Test
public void testSave(){
User user = new User();
user.setUsername("dao impl user");
user.setAddress("北京市顺义区");
user.setSex("男");
user.setBirthday(new Date());
System.out.println("保存操作之前:"+user);
//5.执行保存方法
userDao.saveUser(user);
System.out.println("保存操作之后:"+user);
}
/**
* 测试更新操作
*/
@Test
public void testUpdate(){
User user = new User();
user.setId(50);
user.setUsername("userdaoimpl update user");
user.setAddress("北京市顺义区");
user.setSex("女");
user.setBirthday(new Date());
//5.执行保存方法
userDao.updateUser(user);
}
/**
* 测试删除操作
*/
@Test
public void testDelete(){
//5.执行删除方法
userDao.deleteUser(54);
}
/**
* 测试删除操作
*/
@Test
public void testFindOne(){
//5.执行查询一个方法
User user = userDao.findById(50);
System.out.println(user);
}
/**
* 测试模糊查询操作
*/
@Test
public void testFindByName(){
//5.执行查询一个方法
List<User> users = userDao.findByName("%王%");
for(User user : users){
System.out.println(user);
}
}
/**
* 测试查询总记录条数
*/
@Test
public void testFindTotal(){
//5.执行查询一个方法
int count = userDao.findTotal();
System.out.println(count);
}
}
第6章 SqlMapConfig.xml配置文件
6.1 配置文件里的内容
6.1.1 SqlMapConfig.xml 中配置的内容和顺序
-properties(属性)
–property
-settings(全局配置参数)
–setting
-typeAliases(类型别名)
–typeAliase
–package
-typeHandlers(类型处理器)
-objectFactory(对象工厂)
-plugins(插件)
-environments(环境集合属性对象)
–environment(环境子属性对象)
—transactionManager(事务管理)
—dataSource(数据源)
-mappers(映射器)
–mapper
–package
6.2 properties(属性)数据源数据库
在使用 properties 标签配置时,我们可以采用两种方式指定属性配置。
6.2.1 properties(属性)第一种方法
<properties>
<property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="jdbc.url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="jdbc.username" value="root"/>
<property name="jdbc.password" value="123456"/>
</properties>
使用 如图 :其中property的name可以随便什么名字,只要property标签中${}里面的值一样就好,推荐使用第二种方法。
6.2.2 properties(属性)第二种方法
6.2.2.1 在 classpath 下定义 db.properties 文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=123456
6.2.2.2 properties 标签配置 SqlMapConfig.xml里面
<!-- 配置连接数据库的信息
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:/IdeaProjects/day02_eesy_01mybatisCRUD/src/main/resources/jdbcConfig.prop
erties">
</properties>
2020-6-3
注意:使用这个方法引用外置配置文件properties这个标签一定要在最上面,不然会报错
把它的位置移到配置别名标签下面会出错
6.2.3 此时我们的 dataSource 标签就变成了引用上面的配置
<!--配置连接池-->
<dataSource type="POOLED"> <!--如果使用引用外部配置,那么value要和外部配置的key一样-->
<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>
6.3 typeAliases标签(类型别名)
在前面我们讲的 Mybatis 支持的默认别名,我们也可以采用自定义别名方式来开发。
6.3.1 package标签 自定义别名(实体类别名)
注册实体类的包这样,可以再映射xml中使用类名称,可以不用全限定类名称做为parameterType和resultType的值,直接使用实体类名称(不区分大小写)。
在 SqlMapConfig.xml 中配置:
<typeAliases>
<!-- 单个别名定义 -->
<typeAlias alias="user" type="com.itheima.domain.User"/>
<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) -->
<package name="com.itheima.domain"/><!-- 这样是注册实体类下的所有类 -->
<package name="其它包"/>
</typeAliases>
效果:这就是取别名的好处
不取别名是这样的:
6.4 mappers(映射器)使用于(接口)的
6.4.1 < mapper resource=” ” />
使用相对于类路径的资源
如:< mapper resource=“com/itheima/dao/IUserDao.xml” />
6.4.2 < mapper class=” ” />
使用 mapper 接口类路径
如:< mapper class=“com.itheima.dao.UserDao”/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
6.4.3 < package name=””/> 推荐使用
注册指定包下的所有 mapper 接口
如:< package name=“com.itheima.dao”/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。(推荐使用package这个注册所有dao下的映射xml)
Mybatis 框架课程第三天
第1章 Mybatis 连接池与事务深入
我们在前面的 WEB 课程中也学习过类似的连接池技术,而在 Mybatis 中也有连接池技术,但是它采用的是自己的连接池技术。在 Mybatis 的 SqlMapConfig.xml 配置文件中,通过< dataSource type=”pooled”>来实
现 Mybatis 中连接池的配置。
1.1.1 Mybatis 连接池的分类
在 Mybatis 中我们将它的数据源 dataSource 分为以下几类:
可以看出 Mybatis 将它自己的数据源分为三类:
UNPOOLED 不使用连接池的数据源
POOLED 使用连接池的数据源
JNDI 使用 JNDI 实现的数据源
具体结构如下:
相应地,MyBatis 内部分别定义了实现了 java.sql.DataSource 接口的 UnpooledDataSource,PooledDataSource 类来表示 UNPOOLED、POOLED 类型的数据源。
在这三种数据源中,我们一般采用的是 POOLED 数据源(很多时候我们所说的数据源就是为了更好的管理数据库连接,也就是我们所说的连接池技术)。
1.1.2 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 实例,然后返回使用
1.1.3 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 对象中, 供以后使用。
具体分析过程如下:
1.先进入 XMLConfigBuilder 类中,可以找到如下代码:
2.分析 configuration 对象的 environment 属性,结果如下:
1.1.4 Mybatis 中连接的获取过程分析
当我们需要创建 SqlSession 对象并需要执行 SQL 语句时,这时候 MyBatis 才会去调用 dataSource 对象来创建java.sql.Connection对象。也就是说,java.sql.Connection对象的创建一直延迟到执行SQL语句
的时候。
@Test
public void testSql() throws Exception {
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = factory.openSession();
List<User> list = sqlSession.selectList("findUserById",41);
System.out.println(list.size());
}
只有当第 4 句 sqlSession.selectList(“findUserById”),才会触发 MyBatis 在底层执行下面这个方法来创建 java.sql.Connection 对象。
如何证明它的加载过程呢?
我们可以通过断点调试,在 PooledDataSource 中找到如下 popConnection()方法,如下所示:
分析源代码,得出 PooledDataSource 工作原理如下:
下面是连接获取的源代码:
最后我们可以发现,真正连接打开的时间点,只是在我们执行SQL语句时,才会进行。其实这样做我们也可以进一步发现,数据库连接是我们最为宝贵的资源,只有在要用到的时候,才去获取并打开连接,当我们用完了就再立即将数据库连接归还到连接池中。
1.2 Mybatis 的事务控制
1.2.1 JDBC 中事务的回顾
在 JDBC 中我们可以通过手动方式将事务的提交改为手动方式,通过 setAutoCommit()方法就可以调整。
通过 JDK 文档,我们找到该方法如下:
那么我们的 Mybatis 框架因为是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,本身也是用 JDBC 的 setAutoCommit()方法来设置事务提交方式的。
1.2.2 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()方法来执行提交操作。
1.2.3 Mybatis 自动提交事务的设置
通过上面的研究和分析,现在我们一起思考,为什么 CUD 过程中必须使用sqlSession.commit()提交事务?主要原因就是在连接池中取出的连接,都会将调用 connection.setAutoCommit(false)方法,这样我们就必须使用 sqlSession.commit()方法,相当于使用了 JDBC 中的 connection.commit()方法实现事务提交。
明白这一点后,我们现在一起尝试不进行手动提交,一样实现 CUD 操作。我们只需把openSession(true),这样就修改为自动提交事务了。
@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();
}
所对应的 DefaultSqlSessionFactory 类的源代码:
运行的结果如下:
我们发现,此时事务就设置为自动提交了,同样可以实现CUD操作时记录的保存。虽然这也是一种方式,但就编程而言,设置为自动提交方式为 false 再根据情况决定是否进行提交,这种方式更常用。因为我们可以根据业务情况来决定提交是否进行提交。
第2章 Mybatis 的动态 SQL 语句
Mybatis 的映射文件中,前面我们的 SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的 SQL 是动态变化的,此时在前面的学习中我们的 SQL 就不能满足要求了。
参考的官方文档,描述如下:
2.1 动态 SQL 之< if>标签
我们根据实体类的不同取值,使用不同的 SQL 语句来进行查询。比如在 id 如果不为空时可以根据 id 查询,如果 username 不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。
2.1.1 持久层 Dao 接口
/**
* 根据用户信息,查询用户列表
* @param user
* @return
*/
List<User> findByUser(User user);
2.1.2 持久层 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>
注意:<if>标签的 test 属性中写的是对象的属性名,如果是包装类的对象要使用 OGNL 表达式的写法。
另外要注意 where 1=1 的作用~!
2.1.3 测试
2.2 动态 SQL 之< where>标签
为了简化上面 where 1=1 的条件拼装,我们可以采用标签来简化开发。
2.2.1 持久层 Dao 映射配置
2.3 动态标签之< foreach>标签
2.3.1 用法需求
2.3.1.1 在 QueryVo 中加入一个 List 集合用于封装参数
/**
*
* <p>Title: QueryVo</p>
* <p>Description: 查询的条件</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class QueryVo implements Serializable {
private List<Integer> ids;
public List<Integer> getIds() {
return ids;
}
public void setIds(List<Integer> ids) {
this.ids = ids;
}
}
2.3.2 持久层 Dao 接口
/**
* 根据 id 集合查询用户
* @param vo
* @return
*/
List<User> findInIds(QueryVo vo);
2.3.3 持久层 Dao 映射配置
<!--查询多个id用户-->
<select id="findByIds" resultType="user" parameterType="queryvo"> <!--因为配置文件里面有注册别名使用可以使用类名称不区分大小写-->
select * from user
<where> <!-- 相当于 where 1 = 1 -->
<if test="ids !=null and ids.size>0"><!-- 相当于判断QueryVo 的属性ids 的长度是不是为0 -->
<foreach collection="ids" open="id in (" close=")" item="uid" separator=",">
#{uid} <!-- 这里的uid可以是其他,这里的值看 item的值是什么就是什么-->
</foreach>
<!-- foreach 和遍历差不多
SQL语句:select 字段 from user where id in(?,?,?);
collection属性:相当于要遍历的所有值 (传递属性的值)
open属性:代表语句开始的部分
close属性:代表语句结束的部分
item属性:相当于遍历集合的每一个元素 (相当于 变量 i)
separator属性:代表分隔的符号是什么
-->
</if>
</where>
</select>
2.3.3.1 编写测试方法
2.4 Mybatis 中简化编写的 SQL 片段
Sql 中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的。
2.4.1 定义代码片段
2.4.2 引用代码片段
第3章 Mybatis 多表查询之一对多
3.1 一对一查询(多对一)
3.1.1 方式一 (一对一)
3.1.1.1 定义账户信息的实体类
package com.itmei.domain;
import java.io.Serializable;
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 +
'}';
}
}
3.1.1.2 编写 Sql 语句
3.1.1.3 定义 AccountUser 类
package com.itmei.domain;
public class AccountUser extends Account {
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 + '\'' +
'}';
}
}
3.1.1.4 定义账户的持久层 Dao 接口
package com.itmei.dao;
import com.itmei.domain.Account;
import com.itmei.domain.AccountUser;
import java.util.List;
public interface IAccountDao {
/**
* 查询所有账户,同时获取账户的所属用户名称以及它的地址信息
* @return
*/
List<AccountUser> findAll();
}
3.1.1.5 定义 AccountDao.xml 文件中的查询配置信息
3.1.1.6 创建 AccountTest 测试类
public class AccountTest {
private InputStream in ;
private SqlSessionFactory factory;
private SqlSession session;
private IAccountDao accountDao;
@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的代理对象
userDao = sqlSession.getMapper(IAccountDao.class);
}
@After//用于在测试方法执行之后执行
public void destroy() throws Exception {
//提交事务
//sqlSession.commit();
//6.释放资源
sqlSession.close();
in.close();
}
@Test
public void testFindAll() {
//6.执行操作
List<AccountUser> accountusers = accountDao.findAll();
for(AccountUser au : accountusers) {
System.out.println(au);
}
}
}
3.1.1.7 小结:
定义专门的 po 类作为输出类型,其中定义了 sql 查询结果集所有的字段。此方法较为简单,企业中使用普遍。
3.1.2 方式二 resultMap实现一对一
3.1.2.1 修改 Account 类
在 Account 类中加入 User 类的对象作为 Account 类的一个属性。
3.1.2.2 修改 AccountDao 接口中的方法
3.1.2.3 重新定义 AccountDao.xml 文件
3.1.2.4 在 AccountTest 类中加入测试方法
3.2 一对多查询
3.2.1 编写 SQL 语句
3.2.2 User 类加入 List< Account>
package com.itmei.domain;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
public class User implements Serializable {
private Integer id;
private String username;
private String address;
private String sex;
private Date birthday;
//一对多关系映射:主表实体应该包含从表实体的集合引用
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 String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", address='" + address + '\'' +
", sex='" + sex + '\'' +
", birthday=" + birthday +
'}';
}
}
3.2.3 用户持久层 Dao 接口中加入查询方法
3.2.4 用户持久层 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中的对象类型。此处可以使用别名,也可以使用全限定名。-->
3.2.5 测试方法
package com.itmei.test;
import com.itmei.dao.IAccountDao;
import com.itmei.dao.IUserDao;
import com.itmei.domain.Account;
import com.itmei.domain.AccountUser;
import com.itmei.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;
public class UserTest {
private InputStream in;
private SqlSession sqlSession;
private IUserDao userDao;
@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的代理对象
userDao = sqlSession.getMapper(IUserDao.class);
}
@After//用于在测试方法执行之后执行
public void destroy() throws Exception {
//提交事务
//sqlSession.commit();
//6.释放资源
sqlSession.close();
in.close();
}
/**
* 查询所有账户
* @throws Exception
*/
@Test
public void testFindAll() throws Exception{
List<User> users=userDao.findAll();
for (User user:users){
System.out.println("-----每一个用户信息--------");
System.out.println(user);
System.out.println(user.getAccounts());
}
}
}
运行结果
第4章 Mybatis 多表查询之多对多
4.1 实现 Role 到 User 多对多
4.1.1 用户与角色的关系模型
4.1.2 业务要求及实现 SQL
4.1.3 编写角色实体类
package com.itmei.domain;
import java.io.Serializable;
import java.util.List;
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 + '\'' +
'}';
}
}
4.1.4 编写 Role 持久层接口
4.1.5 IRoleDao.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">
<!--1.上面的是约束-->
<mapper namespace="com.itmei.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 r.id as rid ,r.role_name,r.role_desc ,u.* 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>
4.1.6 编写测试类
package com.itmei.test;
import com.itmei.dao.IRoleDao;
import com.itmei.dao.IUserDao;
import com.itmei.domain.Role;
import com.itmei.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;
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();
}
/**
* 查询角色同时获取用户信息
* @throws Exception
*/
@Test
public void testFindAll() throws Exception{
List<Role> roles=roleDao.findAll();
for (Role role:roles){
System.out.println("-----每一个用户信息--------");
System.out.println(role);
System.out.println(role.getUsers());
}
}
}
运行结果:
4.2 实现 User 到 Role 的多对多
4.2.1 User 到 Role 的多对多
从 User 出发,我们也可以发现一个用户可以具有多个角色,这样用户到角色的关系也还是一对多关系。这样我们就可以认为 User 与 Role 的多对多关系,可以被拆解成两个一对多关系来实现。
4.2.2 作业:实现 User 到 Role 的一对多查询
需求:实现查询所有用户信息并关联查询出每个用户的角色列表
编写用户实体类:
package com.itmei.domain;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
public class User implements Serializable {
private Integer id;
private String username;
private String address;
private String sex;
private Date birthday;
//多对多的关系映射:一个用户可以获取多个角色
private List<Role> roles;
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
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 String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", address='" + address + '\'' +
", sex='" + sex + '\'' +
", birthday=" + birthday +
'}';
}
}
编写 UserDao持久层接口:
UserDao.xml编写映射文件:
这里发现不是使用rid,因为测试发现没有有出现id,检测后发现role里面压根没有rid,那为什么上面的Role到User多对多中使用rid呢,原因是sql语句把role的id 取别名叫成 了 rid,所以不会出现role的id为空的情况,我们这一次在sql里面没有定义role的id别名,所以还是要把column改为id
<?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">
<!--1.上面的是约束-->
<mapper namespace="com.itmei.dao.UserDao">
<!--定义user表的ResultMap-->
<resultMap id="userMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
<!--多对多的关系映射:一个用户可以赋予多个角色-->
<collection property="roles" ofType="role">
<id property="roleId" column="id"></id>
<result property="roleName" column="role_name"></result>
<result property="roleDesc" column="role_desc"></result>
</collection>
</resultMap>
<!--查询语句-->
<select id="findAll" resultMap="userMap">
select u.* ,r.* from user u inner join user_role ur on (u.id=ur.uid)
inner join role r on(r.id=ur.rid);
</select>
</mapper>
编写测试类:
package com.itmei.Test;
import com.itmei.dao.UserDao;
import com.itmei.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class UserTest {
private InputStream in;
private SqlSession session;
private UserDao userDao;
@Before
public void init() throws Exception {
//1.获取配置
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建构建者
SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
//3.创建SqlSession 工厂对象
SqlSessionFactory factory = builder.build(in);
//4.创建SqlSession对象 并且设置自动提交
session = factory.openSession(true);
//5.创建dao的代理对象
userDao= session.getMapper(UserDao.class);
}
@After
public void end() throws Exception{
session.close();
in.close();
}
@Test
public void testFindAll(){
List<User> users= userDao.findAll();
for (User user:users){
System.out.println("-------每一个用户的信息-------");
System.out.println(user);
System.out.println(user.getRoles());
}
}
}
运行结果:
Mybatis 框架课程第四天
第1章 Mybatis 延迟加载策略
通过前面的学习,我们已经掌握了 Mybatis 中一对一,一对多,多对多关系的配置及实现,可以实现对象的关联查询。实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的账户信息。此时就是我们所说的延迟加载。
1.1 何为延迟加载?
1.2 实现需求
1.3 使用 assocation 实现延迟加载(一对一)
需求:查询账户信息同时查询用户信息。
1.3.1 账户的持久层 DAO 接口
package com.itmei.dao;
import com.itmei.domain.Account;
import java.util.List;
public interface IAccountDao {
/**
* 查询所有账户,同时获取账户的所属用户名称以及它的地址信息
* @return
*/
List<Account> findAll();
}
1.3.2 账户的持久层映射文件
1.3.3 用户的持久层接口和映射文件
1.3.4 开启 Mybatis 的延迟加载策略
配置文件设置参数
<!--配置参数-->
<settings>
<!--开启mybatis支持延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--将积极加载改为消息加载即按需加载-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
1.3.5 编写测试只查账户信息不查用户信息。
package com.itmei.test;
import com.itmei.dao.IAccountDao;
import com.itmei.domain.Account;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;
public class AccountTest {
private InputStream in;
private SqlSession sqlSession;
private IAccountDao accountDao;
@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的代理对象
accountDao = sqlSession.getMapper(IAccountDao.class);
}
@After//用于在测试方法执行之后执行
public void destroy() throws Exception {
//提交事务
//sqlSession.commit();
//6.释放资源
sqlSession.close();
in.close();
}
/**
* 查询所有账户,同时还要得到当前账户的所属用户
* @throws Exception
*/
@Test
public void testFindAll() throws Exception{
List<Account> accounts=accountDao.findAll();
}
}
}
1.4 使用 Collection 实现延迟加载(一对多)
1.4.1 在 User 实体类中加入 List属性 一对多
package com.itmei.domain;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
public class User implements Serializable {
private Integer id;
private String username;
private String address;
private String sex;
private Date birthday;
//一对多关系映射:主表实体应该包含从表实体的集合引用
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 String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", address='" + address + '\'' +
", sex='" + sex + '\'' +
", birthday=" + birthday +
'}';
}
}
1.4.2 编写用户和账户持久层接口的方法
1.4.3 编写用户(user)持久层映射配置
1.4.4 编写账户(account)持久层映射配置
1.4.5 测试只加载用户信息
package com.itmei.test;
import com.itmei.dao.IUserDao;
import com.itmei.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;
public class UserTest {
private InputStream in;
private SqlSession sqlSession;
private IUserDao userDao;
@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的代理对象
userDao = sqlSession.getMapper(IUserDao.class);
}
@After//用于在测试方法执行之后执行
public void destroy() throws Exception {
//提交事务
//sqlSession.commit();
//6.释放资源
sqlSession.close();
in.close();
}
/**
* 查询所有账户
* @throws Exception
*/
@Test
public void testFindAll() throws Exception{
List<User> users=userDao.findAll();
}
}
第2章 Mybatis 缓存
2.1 Mybatis 一级缓存
2.1.1 证明一级缓存的存在
一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。
2.1.1.1 编写用户持久层 Dao 接口
package com.itmei.dao;
import com.itmei.domain.User;
import java.util.List;
/**
* 用户的持久层接口
*/
public interface IUserDao {
/**
* 根据id查询用户信息
* @param userId
* @return
*/
User findById(Integer userId);
}
2.1.1.2 编写用户持久层映射文件
2.1.1.3 编写测试方法
package com.itmei.test;
import com.itmei.dao.IUserDao;
import com.itmei.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;
public class UserTest {
private InputStream in;
private SqlSession sqlSession;
private IUserDao userDao;
private SqlSessionFactory factory;
@Before//用于测试方法执行之前执行
public void init() throws Exception {
//1.读取配置,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory工厂
factory = new SqlSessionFactoryBuilder().build(in);
//3.获取SqlSession对象
sqlSession = factory.openSession(true);
//4.获取dao的代理对象
userDao = sqlSession.getMapper(IUserDao.class);
}
@After//用于在测试方法执行之后执行
public void destroy() throws Exception {
//提交事务
//sqlSession.commit();
//6.释放资源
sqlSession.close();
in.close();
}
@Test
public void FindByid(){
User user=userDao.findById(41);
System.out.println("第一次查询的用户:"+user);
User user2=userDao.findById(41);
System.out.println("第一次查询的用户:"+user2);
System.out.println(user==user2);
}
}
2.1.2 一级缓存的分析
一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。
2.1.3 测试一级缓存的清空
验证了当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存,查询数据的操作。
2.2 Mybatis 二级缓存
二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
2.2.1 二级缓存结构图
首先开启 mybatis 的二级缓存。
sqlSession1 去查询用户信息,查询到用户信息会将查询数据存储到二级缓存中。
如果 SqlSession3 去执行相同 mapper 映射下 sql,执行 commit 提交,将会清空该 mapper 映射下的二级缓存区域的数据。
sqlSession2 去查询与 sqlSession1 相同的用户信息,首先会去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
2.2.2 二级缓存的开启与关闭
2.2.2.1 第一步:在 SqlMapConfig.xml 文件开启二级缓存
2.2.2.2 第二步:配置相关的 Mapper 映射文件(接口的xml)
2.2.2.3 第三步:配置 statement 上面的 useCache 属性
2.2.3 二级缓存测试
package com.itmei.test;
import com.itmei.dao.IUserDao;
import com.itmei.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
public class SecondLevelCacheTest {
private InputStream in;
private SqlSessionFactory factory;
@Before//用于测试方法执行之前执行
public void init() throws Exception {
//1.读取配置,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory工厂
factory = new SqlSessionFactoryBuilder().build(in);
}
@After//用于在测试方法执行之后执行
public void destroy() throws Exception {
in.close();
}
/**
* 测试二级缓存
* @throws Exception
*/
@Test
public void testFirstLeveCache() {
SqlSession sqlSession1 = factory.openSession();
IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
User user1=dao1.findById(41);
System.out.println(user1);
sqlSession1.close();//一级缓存消失
SqlSession sqlSession2 = factory.openSession();
IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
User user2=dao2.findById(41);
System.out.println(user1);
sqlSession2.close();//一级缓存消失
System.out.println(user1==user2);
}
}
经过上面的测试,我们发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二次查询时,我们发现并没有对数据库发出 sql 语句,所以此时的数据就只能是来自于我们所说的二级缓存。
2.2.4 二级缓存注意事项
第3章 Mybatis 注解开发
这几年来注解开发越来越流行,Mybatis 也可以使用注解开发方式,这样我们就可以减少编写 Mapper 映射文件了。本次我们先围绕一些基本的 CRUD 来学习,再学习复杂映射关系及延迟加载。
3.1 mybatis 的常用注解说明
3.2 使用 Mybatis 注解实现基本 CRUD
单表的 CRUD 操作是最基本的操作,前面我们的学习都是基于 Mybaits 的映射文件来实现的。
3.2.1 编写实体类
package com.itheima.domain;
import java.io.Serializable;
import java.util.Date;
/**
* @author 黑马程序员
* @Company http://www.ithiema.com
*/
public class User implements Serializable{
private Integer userId;
private String userName;
private String userAddress;
private String userSex;
private Date userBirthday;
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 String getUserAddress() {
return userAddress;
}
public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}
public String getUserSex() {
return userSex;
}
public void setUserSex(String userSex) {
this.userSex = userSex;
}
public Date getUserBirthday() {
return userBirthday;
}
public void setUserBirthday(Date userBirthday) {
this.userBirthday = userBirthday;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userAddress='" + userAddress + '\'' +
", userSex='" + userSex + '\'' +
", userBirthday=" + userBirthday +
'}';
}
}
//注意:
// 此处我们故意和数据库表的列名不一致。
3.2.2 使用注解方式开发持久层接口
3.2.3 编写 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>
3.2.4 编写测试方法
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();
}
/**
*
* <p>Title: MybatisAnnotationCRUDTest</p>
* <p>Description: mybatis 的注解 crud 测试</p>
* <p>Company: http://www.itheima.com/ </p>
*/
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);
}
}
}
3.3 使用注解实现复杂关系映射开发
实现复杂关系映射之前我们可以在映射文件中通过配置< resultMap>来实现,在使用注解开发时我们需要借助@Results 注解,@Result 注解,@One 注解,@Many 注解。
3.3.1 复杂关系映射的注解说明
3.3.2 使用注解实现 (一对一) 复杂关系映射及延迟加载
需求:
加载账户信息时并且加载该账户的用户信息,根据情况可实现延迟加载。(注解方式实现)
3.3.2.1 添加 User 实体类及 Account 实体类
User 实体类
package com.itheima.domain;
import java.io.Serializable;
import java.util.Date;
/**
* @author 黑马程序员
* @Company http://www.ithiema.com
*/
public class User implements Serializable{
private Integer userId;
private String userName;
private String userAddress;
private String userSex;
private Date userBirthday;
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 String getUserAddress() {
return userAddress;
}
public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}
public String getUserSex() {
return userSex;
}
public void setUserSex(String userSex) {
this.userSex = userSex;
}
public Date getUserBirthday() {
return userBirthday;
}
public void setUserBirthday(Date userBirthday) {
this.userBirthday = userBirthday;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userAddress='" + userAddress + '\'' +
", userSex='" + userSex + '\'' +
", userBirthday=" + userBirthday +
'}';
}
}
Account 实体类
package com.itheima.domain;
import java.io.Serializable;
/**
* @author 黑马程序员
* @Company http://www.ithiema.com
*/
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
//多对一(mybatis中称之为一对一)的映射:一个账户只能属于一个用户
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 +
'}';
}
}
3.3.2.2 添加账户的持久层接口并使用注解配置
3.3.2.3 添加用户的持久层接口并使用注解配置
3.3.2.4 测试一对一关联及延迟加载
按正常道理当调用了findAll方法就会执行注解的所有数据,但是通过fetchType=FetchType.LAZY 实现延迟加载,在进行表的关联查询时,按照设置延迟规则推迟对关联对象的select查询。例如在进行一对多查询的时候,只查询出一方,当程序中需要多方的数据时,mybatis再发出sql语句进行查询,这样子延迟加载就可以的减少数据库压力。
3.3.3 使用注解实现 (一对多) 复杂关系映射
需求:
查询用户信息时,也要查询他的账户列表。使用注解方式实现。
分析:
一个用户具有多个账户信息,所以形成了用户(User)与账户(Account)之间的一对多关系。
3.3.3.1 User 实体类加入 List< Account>
3.3.3.2 编写用户的持久层接口并使用注解配置
3.3.3.3 编写账户的持久层接口并使用注解配置
3.3.3.4 添加测试方法
package com.itheima.test;
import com.itheima.dao.IUserDao;
import com.itheima.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;
/**
* @author 黑马程序员
* @Company http://www.ithiema.com
*/
public class UserTest {
private InputStream in;
private SqlSessionFactory factory;
private SqlSession session;
private IUserDao userDao;
@Before//用于测试方法执行之前执行
public void init() throws Exception {
//1.读取配置,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory工厂
factory = new SqlSessionFactoryBuilder().build(in);
//3.获取SqlSession对象
session = factory.openSession(true);
//4.获取dao的代理对象
userDao = session.getMapper(IUserDao.class);
}
@After//用于在测试方法执行之后执行
public void destroy() throws Exception {
//提交事务
session.commit();
//6.释放资源
session.close();
in.close();
}
@Test
public void testFindAll(){
List<User> users = userDao.findAll();
// for(User account : users){
// System.out.println("----每个账户的信息-----");
// System.out.println(account);
// System.out.println(account.getAccounts());
// }
}
}
运行结果:因为使用延迟加载,如果使for循环可以使用那么会调用方法查询用户的信息,简称按需加载。
3.4 mybatis 基于注解的二级缓存
3.4.1 在 SqlMapConfig 中开启二级缓存支持
3.4.2 在持久层接口中使用注解配置二级缓存
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/83929.html