框架学习二:Mybatis
一、映射
1.一对一映射:
模型:用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户。
需求:查询一个订单,与此同时查询出该订单所属的用户。
2.一对一查询SQL语句:
sql语句:select * from orders o,user u where o.uid=u.id;
3.创建Order和User实体类
public class Order {
private int id;//订单编号
private String ordertime;//订单时间
private double total;//订单总价
private int uid;//用户编号
@Override
public String toString() {
return "Order [id=" + id + ", ordertime=" + ordertime + ", total=" + total + ", uid=" + uid + /*", user="
+ user + */"]";
}
public String getOrdertime() {
return ordertime;
}
public void setOrdertime(String ordertime) {
this.ordertime = ordertime;
}
public double getTotal() {
return total;
}
public void setTotal(double total) {
this.total = total;
}
public int getuid() {
return uid;
}
public void setuid(int uid) {
this.uid = uid;
}
}
public class User{
private int id;//用户编号
private String username;//用户名
private String birthday;//出生日期
private String password;//密码
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", birthday=" + birthday + ", password=" + password
+ ", role=" + role + "]";
}
}
4.创建OrderMapper接口
import java.util.List;
import com.it.bean.Order;
public interface OrderMapper {
/**
* 查询订单信息以及相关用户信息
* */
public List<Order> selectOrderAndUser();
}
5.配置OrderMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 引入映射配置文件的dtd约束 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.it.mapper.OrderMapper">
<!-- 定义resultMap完成一一对应映射关系 -->
<resultMap type="order" id="orderAndUser">
<id column="id" property="id"/>
<result column="ordertime" property="ordertime"/>
<result column="total" property="total"/>
<result column="userid" property="uid"/>
<!-- 使用association标签完成一对一映射
property属性:在order中定义user对象的属性名
javaType属性:表示关联对象属性所属的java类型
-->
<association property="user" javaType="com.it.bean.User">
<id column="userid" property="id"/>
<result column="username" property="username"/>
<result column="birthday" property="birthday"/>
<result column="password" property="password"/>
</association>
</resultMap>
<!-- 查询订单信息以及相关用户 -->
<select id="selectOrderAndUser" resultMap="orderAndUser">
select o.*,u.id userid,u.username,u.birthday,u.password from orders o, tuser u where o.userid=u.id
</select>
</mapper>
6.测试
@Test
public void selectOrderAndUser() throws IOException{
//加载全局配置文件
Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
//创建会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
//打开会话
SqlSession session = factory.openSession();
//获取Mapper接口的代理方法
OrderMapper mapper= session.getMapper(OrderMapper.class);
//调用响应方法
List<Order> orders = mapper.selectOrderAndUser();
for(Order order:orders){
System.out.println(order);
}
//关闭会话
session.close();
}
一对多查询模型:
1.模型:用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户。
需求:查询一个用户,与此同时查询出该用户具有的订单。
2.一对多查询的SQL语句
sql语句:select *,o.id oid from user u left join orders o on u.id=o.uid;
3.修改实体类
public class Order {
private int id;//订单编号
private String ordertime;//订单时间
private double total;//订单总价
private int uid;//用户编号
private User user;//当前订单信息属于哪个用户
@Override
public String toString() {
return "Order [id=" + id + ", ordertime=" + ordertime + ", total=" + total + ", uid=" + uid + /*", user="
+ user + */"]";
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getOrdertime() {
return ordertime;
}
public void setOrdertime(String ordertime) {
this.ordertime = ordertime;
}
public double getTotal() {
return total;
}
public void setTotal(double total) {
this.total = total;
}
public int getuid() {
return uid;
}
public void setuid(int uid) {
this.uid = uid;
}
}
public class User{
private int id;//用户编号
private String username;//用户名
private String birthday;//出生日期
private String password;//密码
//表示用户下了那些订单
private List<Order> orders;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public List<Order> getOrders() {
return orders;
}
public void setOrders(List<Order> orders) {
this.orders = orders;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", birthday=" + birthday + ", password=" + password
+ ", role=" + role + "]";
}
}
4.创建UserMapper接口
/**
* 查询用户信息以及所下的订单信息
* */
public List<User> selectUserAndOrders();
5.配置UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 引入映射配置文件的dtd约束 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.it.mapper.UserMapper">
<!-- resultMap+collection:一对多 -->
<resultMap type="user" id="userAndOrders">
<id column="userid" property="id"/>
<result column="username" property="username"/>
<result column="birthday" property="birthday"/>
<result column="password" property="password"/>
<!-- collection标签:完成一对多关系映射
property属性:表示在User实体类中定义的存储多个订单信息的集合属性名
-->
<collection property="orders" ofType="order">
<id column="id" property="id"/>
<result column="ordertime" property="ordertime"/>
<result column="total" property="total"/>
<result column="userid" property="uid"/>
</collection>
</resultMap>
<!-- 调查用户以及相关订单信息 -->
<select id="selectUserAndOrders" resultMap="userAndOrders">
select u.id userid, u.username, u.birthday,u.password, o.* from tuser u left join orders o on u.id=o.userid
</select>
6.测试结果
@Test
public void selectUserAndOrders() throws IOException{
//加载全局配置文件
Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
//创建会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
//打开会话
SqlSession session = factory.openSession();
//获取Mapper接口的代理方法
UserMapper mapper= session.getMapper(UserMapper.class);
//调用响应方法
List<User> users = mapper.selectUserAndOrders();
for(User user:users){
System.out.println(user);
}
//关闭会话
session.close();
}
多对多查询
1.多对多查询模型:
用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用。
需求:查询用户同时查询出该用户的所有角色。
2.多对多查询的语句
对应的sql语句:select u.,r.,r.id rid from user u left join user_role ur on u.id=ur.user_id
inner join role r on ur.role_id=r.id;
3.创建Role实体类,修改User类
public class User implements Serializable{
private int id;//用户编号
private String username;//用户名
private String birthday;//出生日期
private String password;//密码
private List<Role> role;//角色
public List<Role> getRole() {
return role;
}
public void setRole(List<Role> role) {
this.role = role;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", birthday=" + birthday + ", password=" + password
+ ", role=" + role + "]";
}
}
public class Role {
private int id;//角色id
private String rolename;//角色名称
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getRolename() {
return rolename;
}
public void setRolename(String rolename) {
this.rolename = rolename;
}
@Override
public String toString() {
return "Role [id=" + id + ", rolename=" + rolename + "]";
}
}
4.添加UserMapper结构方法
/**
* 查询用户信息以及所下的订单信息
* */
public List<User> selectUserAndOrders();
5.配置UserMapper.xml
<!-- 查询用户及其所有角色信息 -->
<resultMap type="user" id="UserAndRoles">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="birthday" property="birthday"/>
<result column="password" property="password"/>
<!-- collection标签:完成一对多关系映射
property属性:表示在User实体类中定义的存储多个订单信息的集合属性名
-->
<collection property="role" ofType="role">
<id column="roleid" property="id"/>
<result column="rolename" property="rolename"/>
</collection>
</resultMap>
<select id="selectUserAndRoles" resultMap="UserAndRoles">
select u.*,r.id roleid,r.rolename from tuser u, user_role ur, role r where u.id = ur.user_id and ur.role_id=r.id order by u.id
</select>
6.测试结构
@Test
public void selectUserAndRoles() throws IOException{
//加载全局配置文件
Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
//创建会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
//打开会话
SqlSession session = factory.openSession();
//获取Mapper接口的代理方法
UserMapper mapper= session.getMapper(UserMapper.class);
//调用响应方法
List<User> users = mapper.selectUserAndRoles();
for(User user:users){
System.out.println(user);
}
//关闭会话
session.close();
}
总结:
MyBatis多表配置方式:
一对一配置:使用<resultMap>+<association>做配置
一对多配置:使用<resultMap>+<collection>做配置
多对多配置:使用<resultMap>+<collection>做配置
缓存
1.查询缓存
Mybatis提供查询缓存,用于减轻数据库压力,提高数据库性能
Mybatis提供一级缓存,和二级缓存
一级缓存是SqlSession级别的缓存
在操作数据库时需要构造SqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据
不同的SqlSession之间的缓存数据区域是互不影响的.
二级缓存是Mapper级别的缓存,多个SqlSession去操作同一个Mapper的Sql语句,多个SqlSession可以 共用二级缓存,二级级缓存是跨SqlSession的.
为什么要用缓存:如果缓存中有数据就不用从数据库中获取,大大提高系统性能.
一级缓存
(1)一级缓存的工作原理: 一级缓存的使用范围是:SqlSession;
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库中 查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
如果SqlSession去执行commit(执行插入,修改,删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新信息,避免脏读。
第二次发起查询用户id为1的用户信息,先去找缓存中对否有id为1的用户信息,缓存中有,直接从缓存 中获取用户信息。
一级缓存测试: Mybatis默认支持一级缓存,不需要在配置文件去配置。
@Test
public void selectUserById() throws IOException{
//加载全局配置文件
Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
//创建会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
//打开会话
SqlSession session = factory.openSession();
//获取Mapper接口的代理方法
UserMapper mapper= session.getMapper(UserMapper.class);
//第一次查询id为1的用户信息,先去缓存中查找,没有再去数据库中查找
User user = mapper.selectUserById(1);
System.out.println(user);
//第二次查询id为1的用户,先去缓存中查找,有则直接从缓存中取
User user1 = mapper.selectUserById(1);
System.out.println(user1);
//关闭会话
session.close();
}
二级缓存
(1)原理:首先开启mybatis二级缓存
Sqlsession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中。
如果SQLSession3去执行相同Mapper下的sql,执行commit提交,清空该Mapper下的二级缓存区域的 数据。
Sqlsession2去查询用户id为1的用户信息,去缓存中找是否存在该数据,如果存在直接从缓存中取出数据。
二级缓存与一级缓存区别:二级缓存范围更大,多个SqlSession可以共享一个UserMapper的二级缓存区域。
UserMapper有一个二级缓存区域,其它Mapper也有自己的二级缓存区域(注意:如果两个Mapper有相同的 namespace使用的是同一个二级缓存区域,这两个Mapper执行SQL查询到数据将存在相同的二级缓存区域中)
开启二级缓存:Mybatis的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的Mapper.xml中开启二级缓存
在SqlMapConfig.xml中配置:
<settings>
<!-- 开启二级缓存(总标签) -->
<setting name="cacheEnabled" value="true"/>
</settings>
在UserMapper.xml中开启二级缓存,UserMapper.xml下的sql语句执行完成会存储到它的缓存区域
<!-- 开启UserMapper的二级缓存 -->
<cache></cache>
调用pojo类实现序列化接口
import java.io.Serializable;
import java.util.List;
/**
* 需要先进行实现序列化接口
* */
public class User implements Serializable{...}
序列化原因:为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一样在内存。
测试方法:
@Test
public void selectUserById2() throws IOException{
//加载全局配置文件
Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
//创建会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
//打开会话
SqlSession session1 = factory.openSession();
SqlSession session2 = factory.openSession();
SqlSession session3 = factory.openSession();
//获取Mapper接口的代理方法
UserMapper mapper1= session1.getMapper(UserMapper.class);
UserMapper mapper2= session2.getMapper(UserMapper.class);
UserMapper mapper3= session3.getMapper(UserMapper.class);
//调用响应方法
//由session1发起的第一次查询id为1的用户信息
User user1 = mapper1.selectUserById(1);
System.out.println(user1);
//关闭会话
session1.close();
User user3 = new User();
user3.setId(1);
user3.setBirthday("1001-01-10");
int restult = mapper3.updateUserById(user3);
//执行一次commit
session3.commit();
//关闭会话
session3.close();
//由session2发起的第一次查询id为1的用户信息
User user2 = mapper2.selectUserById(1);
System.out.println(user2);
//关闭会话
session2.close();
}
useCache设置
在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql语 句,默认情况是true,即该sql使用二级缓存
总结:
针对每次查询时都需要最新的数据的sql要设置成useCache=false
刷新缓存(就是清空缓存)
一般执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。
二级缓存应用场景
对查询频率高,变化频率低的数据建议使用二级缓存。
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。
实现方法:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。
mybatis二级缓存局限性
mybatis二级缓存对细粒度的数据级别的缓存实现不好。
比如:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。
Mybatis注解
Mybatis也可以使用注解开发方式,这样我们就可以减少编写Mapper映射文件了。
1.MyBatis的常用注解
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
2.使用注解进行crud操作
/**
* 根据id查询用户信息
* */
@Select("select * from tuser where id=#{id}")
public User selectUserById(int id);
/**
* 修改用户信息
* */
@Update("update tuser set birthday=#{birthday} where id=#{id}")
public int updateUserById(User user);
/**
* 新增用户信息
* */
@Insert("insert into tuser values(seq_user.nextval,#{username},#{password},#{birthday})")
public int insertUser(User user);
/**
* 根据用户ID删除用户信息
* */
@Delete("delete from tuser where id=#{id}")
public int deleteUserById(int id);
/**
* 根据用户名和密码登录
* */
@Select("select * from tuser where username=#{username} and password=#{password}")
public User login(@Param("username")String loginname,@Param("password")String password);
/**
* 查询用户信息
* */
@Select("select * from tuser")
public List<User> selectUsers();
不使用@Param注解时,参数只能有一个。如果想携带多个参数需要使用@Param注解,@Param(“paramName”) 中的paramName即为#{}中参数名
如果查询结果集中的字段名和输出结果映射pojo的属性名不一致时,需要配置结果集映射。