SSM框架系列文章:
SSM框架学习总结第零篇–框架是什么
SSM框架学习总结第一篇–MyBatis
SSM框架学习总结第二篇–Spring
SSM框架学习总结第三篇–Spring MVC
SSM框架学习总结第四篇–SSM框架整合
提示:以下是本篇文章正文内容,下面案例可供参考
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","ro
ot", "root");
//定义 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 的原始方法(未经封装)实现了查询数据库表记录的操作。
在pom.xml文件中添加MyBatis3.4.5的坐标
代码如下:
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
</dependencies>
代码如下:
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 + "]";
}
}
MyBatis的使用有两种方式,第一种是基于映射的方法,第二种是基于注解的方法。前者需要编写两个配置文件,后者只需要编写一个注释文件,因此后者更加简单。
IUserDao 接口就是我们的持久层接口(也可以写成 UserDao 或者 UserMapper) ,具体代码如下:
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
List<User> findAll();
}
要求:
创建位置: 必须和持久层接口在相同的包中。
名称: 必须以持久层接口名称命名文件名,扩展名是.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.IUserDao">
<!-- 配置查询所有操作 -->
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
</select>
</mapper>
代码如下:
<?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>
<!-- 配置 mybatis 的环境 -->
<environments default="mysql">
<!-- 配置 mysql 的环境 -->
<environment id="mysql">
<!-- 配置事务的类型 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接数据库的信息:用的是数据源(连接池) -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ee50"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<!-- 告知 mybatis 映射配置的位置 -->
<mappers>
<mapper resource="com/itheima/dao/IUserDao.xml"/>
</mappers>
</configuration>
代码如下:
public class MybatisTest {
public static void main(String[] args)throws Exception {
//1.读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建 SqlSessionFactory 的构建者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.使用构建者创建工厂对象 SqlSessionFactory
SqlSessionFactory factory = builder.build(in);
//4.使用 SqlSessionFactory 生产 SqlSession 对象
SqlSession session = factory.openSession();
//5.使用 SqlSession 创建 dao 接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
//6.使用代理对象执行查询所有方法
List<User> users = userDao.findAll();
for(User user : users) {
System.out.println(user);
}
//7.释放资源
session.close();
in.close();
}
}
代码如下:
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
@Select("select * from user")
List<User> findAll();
}
代码如下:
<!-- 告知 mybatis 映射配置的位置 -->
<mappers>
<mapper class="com.itheima.dao.IUserDao"/>
</mappers>
使用基于注解的Mybatis 配置时,请移除 xml 的映射配置(IUserDao.xml)。
通过以上例子,我们可以看出使用MyBatis是一件很容易的事情,因为只需要我们表写DAO接口,并按照MyBatis编写两个配置文件,就可以实现功能。远比JDBC方便得多。如果使用注解的模式就更加简单了,只需要编写一个配置文件就可以了。
参考:工厂模式——这一篇真够了
工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。著名的Jive论坛 ,就大量使用了工厂模式,工厂模式在Java程序系统可以说是随处可见。因为工厂模式就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A() 工厂模式也是用来创建实例对象的,所以以后new时就要多个心眼,是否可以考虑使用工厂模式,虽然这样做,可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。
在介绍简单工厂模式之前,我们尝试解决以下问题:
现在我们要使用面向对象的形式定义计算器,为了实现各算法之间的解耦。我们一般会这么写:
// 计算类的基类
@Setter
@Getter
public abstract class Operation {
private double value1 = 0;
private double value2 = 0;
protected abstract double getResule();
}
//加法
public class OperationAdd extends Operation {
@Override
protected double getResule() {
return getValue1() + getValue2();
}
}
//减法
public class OperationSub extends Operation {
@Override
protected double getResule() {
return getValue1() - getValue2();
}
}
//乘法
public class OperationMul extends Operation {
@Override
protected double getResule() {
return getValue1() * getValue2();
}
}
//除法
public class OperationDiv extends Operation {
@Override
protected double getResule() {
if (getValue2() != 0) {
return getValue1() / getValue2();
}
throw new IllegalArgumentException("除数不能为零");
}
}
当我们要使用这个计算器的时候,又会这么写:
public static void main(String[] args) {
//计算两数之和
OperationAdd operationAdd = new OperationAdd();
operationAdd.setValue1(1);
operationAdd.setValue2(2);
System.out.println("sum:"+operationAdd.getResule());
//计算两数乘积
OperationMul operationMul = new OperationMul();
operationMul.setValue1(3);
operationMul.setValue2(5);
System.out.println("multiply:"+operationMul.getResule());
//计算两数之差。。。
}
缺点:该计算器的使用者需要知道实现加法逻辑的那个类的具体名字
想要使用不同的运算的时候就要创建不同的类,并且要明确知道该类的名字。那么这种重复的创建类的工作其实可以放到一个统一的类中去管理。这样的方法我们就叫做「简单工厂模式」,在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为「静态工厂方法」模式。简单工厂模式有以下优点:
简单工厂模式包含 3 个角色(要素):
现在我们定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类:
//工厂类
public class OperationFactory {
public static Operation createOperation(String operation) {
Operation oper = null;
switch (operation) {
case "add":
oper = new OperationAdd();
break;
case "sub":
oper = new OperationSub();
break;
case "mul":
oper = new OperationMul();
break;
case "div":
oper = new OperationDiv();
break;
default:
throw new UnsupportedOperationException("不支持该操作");
}
return oper;
}
}
有了工厂类之后,可以使用工厂创建对象:
public static void main(String[] args) {
Operation operationAdd = OperationFactory.createOperation("add");
operationAdd.setValue1(1);
operationAdd.setValue2(2)
System.out.println(operationAdd.getResule());
}
通过简单工厂模式,该计算器的使用者不需要关系实现加法逻辑的那个类的具体名字,只要知道该类对应的参数"add"就可以了。这就体现了之前提到的工厂模式的优点。
存在问题:
我们常说的工厂模式,就是指「工厂方法模式」,也叫「虚拟构造器模式」或「多态工厂模式」。
工厂方法模式包含 4 个角色(要素):
从UML类图可以看出,相比于简单工厂模式,每种产品实现,我们都要增加一个继承于工厂接口 IFactory 的工厂类 Factory ,修改简单工厂模式代码中的工厂类如下:
//工厂接口
public interface IFactory {
Operation CreateOption();
}
//加法类工厂
public class AddFactory implements IFactory {
public Operation CreateOption() {
return new OperationAdd();
}
}
//减法类工厂
public class SubFactory implements IFactory {
public Operation CreateOption() {
return new OperationSub();
}
}
//乘法类工厂
public class MulFactory implements IFactory {
public Operation CreateOption() {
return new OperationMul();
}
}
//除法类工厂
public class DivFactory implements IFactory {
public Operation CreateOption() {
return new OperationDiv();
}
}
我们使用计算器的时候,要为每种运算方法增加一个工厂对象::
public class Client {
public static void main(String[] args) {
//减法
IFactory subFactory = new SubFactory();
Operation operationSub = subFactory.CreateOption();
operationSub.setValue1(22);
operationSub.setValue2(20);
System.out.println("sub:"+operationSub.getResult());
//除法
IFactory Divfactory = new DivFactory();
Operation operationDiv = Divfactory.CreateOption();
operationDiv.setValue1(99);
operationDiv.setValue2(33);
System.out.println("div:"+operationSub.getResult());
}
}
适用场景:
工厂方法模式通过工厂来创建对象,工厂方法模式在设计上完全完全符合“开闭原则”。
在以下情况下可以使用工厂方法模式:
使用场景
总结
优点:
缺点:
工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问题,但由于工厂方法模式中的每个工厂只生产一类产品,可能会导致系统中存在大量的工厂类,势必会增加系统的开销。此时,我们可以考虑将一些相关的产品组成一个“产品族”,由同一个工厂来统一生产,这就是抽象工厂模式的基本思想。
定义
为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。
抽象工厂(Abstract Factory)模式,又称工具箱(Kit 或Toolkit)模式。
实现方法
抽象工厂模式是工厂方法模式的升级版本,他用来创建一组相关或者相互依赖的对象。他与工厂方法模式的区别就在于,工厂方法模式针对的是一个产品等级结构;而抽象工厂模式则是针对的多个产品等级结构。在编程中,通常一个产品结构,表现为一个接口或者抽象类,也就是说,工厂方法模式提供的所有产品都是衍生自同一个接口或抽象类,而抽象工厂模式所提供的产品则是衍生自不同的接口或抽象类。
在抽象工厂模式中,有一个产品族的概念:所谓的产品族,是指位于不同产品等级结构中功能相关联的产品组成的家族。抽象工厂模式所提供的一系列产品就组成一个产品族;而工厂方法提供的一系列产品称为一个等级结构。
也没骗你,抽象工厂模式确实是抽象。
抽象工厂模式包含的角色(要素)
UML类图
抽象工厂模式适用场景
抽象工厂模式和工厂方法模式一样,都符合开闭原则。 但是不同的是,工厂方法模式在增加一个具体产品的时候,都要增加对应的工厂。但是抽象工厂模式只有在新增一个类型的具体产品时才需要新增工厂。 也就是说,工厂方法模式的一个工厂只能创建一个具体产品。而抽象工厂模式的一个工厂可以创建属于一类类型的多种具体产品。工厂创建产品的个数介于简单工厂模式和工厂方法模式之间。
在以下情况下可以使用抽象工厂模式:
“开闭原则”的倾斜性
在抽象工厂模式中,增加新的产品族很方便,但是增加新的产品等级结构很麻烦,抽象工厂模式的这种性质称为 “开闭原则”的倾斜性 。“开闭原则”要求系统对扩展开放,对修改封闭,通过扩展达到增强其功能的目的,对于涉及到多个产品族与多个产品等级结构的系统,其功能增强包括两方面:
正因为抽象工厂模式存在“开闭原则”的倾斜性,它以一种倾斜的方式来满足“开闭原则”,为增加新产品族提供方便,但不能为增加新产品结构提供这样的方便,因此要求设计人员在设计之初就能够全面考虑,不会在设计完成之后向系统中增加新的产品等级结构,也不会删除已有的产品等级结构,否则将会导致系统出现较大的修改,为后续维护工作带来诸多麻烦。
总结:
抽象工厂模式是工厂方法模式的进一步延伸,由于它提供了功能更为强大的工厂类并且具备较好的可扩展性,在软件开发中得以广泛应用,尤其是在一些框架和API类库的设计中,例如在Java语言的AWT(抽象窗口工具包)中就使用了抽象工厂模式,它使用抽象工厂模式来实现在不同的操作系统中应用程序呈现与所在操作系统一致的外观界面。抽象工厂模式也是在软件开发中最常用的设计模式之一。
优点:
我们最常用的 Spring 就是一个最大的 Bean 工厂,IOC 通过BeanFactory对Bean 进行管理。
我们使用的日志门面框架slf4j,点进去就可以看到熟悉的味道
JDK 的 Calendar 使用了简单工厂模式
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
创建一个接口:
//Image.java
public interface Image {
void display();
}
创建实现接口的实体类:
//RealImage.java
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName){
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName){
System.out.println("Loading " + fileName);
}
}
//ProxyImage.java
public class ProxyImage implements Image{
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName){
this.fileName = fileName;
}
@Override
public void display() {
if(realImage == null){
realImage = new RealImage(fileName);
}
realImage.display();
}
}
当被请求时,使用 ProxyImage 来获取 RealImage 类的对象:
//ProxyPatternDemo.java
public class ProxyPatternDemo {
public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg");
// 图像将从磁盘加载
image.display();
System.out.println("");
// 图像不需要从磁盘加载
image.display();
}
}
执行程序,输出结果:
Loading test_10mb.jpg
Displaying test_10mb.jpg
Displaying test_10mb.jpg
动态代理
Java动态代理InvocationHandler和Proxy学习笔记
java动态代理
这是三篇关于动态代理的文章,很推荐初学者阅读。
总结:
设计模式之创建者模式
UML图:
组成:
抽象创建者角色:给出一个抽象接口,以规范产品对象的各个组成成分的建造。一般而言,此接口独立于应用程序的商业逻辑。模式中直接创建产品对象的是具体创建者角色。具体创建者必须实现这个接口的两种方法:一是建造方法,比如图中的buildPart1 和buildPart2 方法;另一种是结果返回方法,即图中的 getProduct 方法。一般来说,产品所包含的零件数目与建造方法的数目相符。换言之,有多少零件,就有多少相应的建造方法。
具体创建者角色: 他们在应用程序中负责创建产品的实例。这个角色要完成的任务包括:
(1)实现抽象创建者所声明的抽象方法,给出一步一步的完成产品创建实例的操作。
(2)在创建完成后,提供产品的实例。
导演者角色: 这个类调用具体创建者角色以创建产品对象。但是导演者并没有产品类的具体知识,真正拥有产品类的具体知识的是具体创建者角色。
产品角色: 产品便是建造中的复杂对象。一般说来,一个系统中会有多于一个的产品类,而且这些产品类并不一定有共同的接口,而完全可以使不相关联的。
优点:
①使用建造者模式可以使客户端不必知道产品内部组成的细节。
②具体的建造者类之间是相互独立的,这有利于系统的扩展。
③具体的建造者相互独立,因此可以对建造的过程逐步细化,而不会对其他模块产生任何影响。
使用场景:
①创建一些复杂对象时,这些对象的内部组成部分之间的建造顺序是稳定的,但对象的内部组成构建面临着复杂的变化。
②要创建的复杂对象的算法,独立于该对象的组成部分,也独立于组成部分的装配方法时。
这部分是MyBatis的进阶使用说明,包括一对一查询,一对多查询,以及MyBatis的缓存机制。
以下的例子主要使用注释讲解。
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider: 实现动态 SQL 映射
@CacheNamespace:实现注解二级缓存的使用
复杂关系映射的注解说明
@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 + "]"; } }
/**
账户的实体类
*/
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 {
/**
* 查询所有账户,采用延迟加载的方式查询账户的所属用户
* Results属性介绍:
* id="accountMap"代表结果集的名称
* value代表结果集的主要内容
* Result属性介绍:
* id 是否是主键字段,id为true是代表为主键
* column 数据库的列名
* property 需要装配的属性名
* fetchType=FetchType.LAZY表示才用延迟加载的功能
* one=@One的作用的关联通过uid关联用户表
* account里面有一个属性user,而account和user的关系是一对一对应
* 因此这里使用的是one=@One的注释,通过uid,我们就可以找到唯一对应的user
* one=@One里面的select就是我们通过uid查找user的方法
* @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 {
/**
* 查询所有用户
* Results属性介绍:
* id="userMap"代表结果集的名称
* value代表结果集的主要内容
* Result属性介绍:
* id 是否是主键字段,id为true是代表为主键
* column 数据库的列名
* property 需要装配的属性名
* fetchType=FetchType.LAZY表示才用延迟加载的功能
* @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
* select语句中需要使用到Integer数据类型的参数
* 但sql语句的参数为基本参数且只有一个参数时,可以随意命名,这里命名为uid
* ResultMap这里选择了名字为userMap的结果集来封装查询结果
* @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());
// }
}
需求:
查询用户信息时,也要查询他的账户列表。使用注解方式实现。
分析:
与第一部分的区别在于,第一部分实现的是account到user的映射,一个account账户只能属于一个user用户。
本需求是实现user到account的映射,一个user可以拥有多个account,所以形成了用户(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 {
/**
* 查询所有用户
* column 数据库的列名
* property 需要装配的属性名,根据传入数据(数据库的列名column ),将结果封装的属性
* @Many属性:
* 相当于(xml方式)的配置
* select 属性:代表将要执行的 sql 语句
* fetchType 属性:代表加载方式,一般如果要延迟加载都设置为 LAZY 的值
* @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();
}
编写账户的持久层接口并使用注解配置
/**
* 账户的持久层接口
*/
public interface IAccountDao {
/**
* 根据用户 id 查询用户下的所有账户
* @param userId
* @return
*/
@Select("select * from account where uid = #{uid} ")
List<Account> findByUid(Integer userId);
}
添加测试方法
/**
* mybatis 的注解 crud 测试
*/
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();
}
}
像大多数的持久化框架一样,Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。
Mybatis 中缓存分为一级缓存,二级缓存。
说明:缓存机制的例子都是通过映射文件方法编写的。
一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。
编写用户持久层 Dao 接口
/**
* 用户的业务层接口
*/
public interface IUserDao {
/**
* 根据 id 查询
* @param userId
* @return
*/
User findById(Integer userId);
}
通过映射文件中:useCache=“true”,我们就可以开启一级缓存。
编写用户持久层映射文件
<?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" useCache="true">
select * from user where id = #{uid}
</select>
</mapper>
编写测试方法
/**
*
* Title: MybastisCRUDTest
* Description: 一对多的操作
* Company: http://www.itheima.com/
*/
public class UserTest {
private InputStream in ;
private SqlSessionFactory factory;
private SqlSession session;
private IUserDao userDao;
@Test
public void testFindById() {
//6.执行操作
User user = userDao.findById(41);
System.out.println("第一次查询的用户:"+user);
User user2 = userDao.findById(41);
System.out.println("第二次查询用户:"+user2);
System.out.println(user == user2);
}
@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.close();
in.close();
}
}
测试结果如下:
我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是 MyBatis 提供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询 id 为 41 的记录时,并没有发出 sql 语句从数据库中查询数据,而是从一级缓存中查询。
注意:一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
第一次发起查询用户 id 为1的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。
二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
MyBatis的二级缓存使用可以分为三步:
第一步:在 SqlMapConfig.xml 文件开启二级缓存
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为
false 代表不开启二级缓存。
第二步:配置相关的 Mapper 映射文件
<cache>标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。
<?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">
<!-- 开启二级缓存的支持 -->
<cache></cache>
</mapper>
第三步:配置 statement 上面的 useCache 属性
<!-- 根据 id 查询 --> <select id="findById" resultType="user" parameterType="int" useCache="true">
select * from user where id = #{uid}
</select>
将 UserDao.xml 映射文件中的<select>标签中设置 useCache=”true”代表当前这个statement要使用二级缓存,如果不使用二级缓存可以设置为false。
注意:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存。
二级缓存测试
/**
* @author 黑马程序员
* @Company http://www.ithiema.com
*/
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();
}
/**
* 测试一级缓存
*/
@Test
public void testFirstLevelCache(){
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(user2);
sqlSession2.close();
System.out.println(user1 == user2);
}
}
经过上面的测试,我们发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二次查询时,我们发现并没有对数据库发出 sql 语句,所以此时的数据就只能是来自于我们所说的二级缓存。
二级缓存注意事项
当我们在使用二级缓存时,所缓存的类一定要实现java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。
通过以上的讲解,MyBatis的缓存机制可以用一张图就能概括出来,如下图所示:
MyBatis 基于注解的二级缓存的设置很简单,只需要两步:
第一步:在 SqlMapConfig 中开启二级缓存支持
<!-- 配置二级缓存 --> <settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
第二步:在持久层接口中使用注解配置二级缓存
/**
* 用户的持久层接口
*/
@CacheNamespace(blocking=true)//mybatis 基于注解方式实现配置二级缓存
public interface IUserDao {
}
如果各位大佬觉得有用,请给个赞吧。