MyBatis 是一个开源的持久层框架,它简化了数据库交互的过程。与许多其他持久层框架不同,MyBatis 不会强制你使用对象关系映射(ORM)的范式。它采用了一种将 Java 对象和数据库记录进行映射的灵活方式,可以通过 XML 或者注解配置 SQL 映射关系,使得开发者可以自由地定制 SQL,而不需要像 Hibernate 那样需要遵循特定的对象模型。
以下是 MyBatis 的一些主要特点和优势:
简单易学: MyBatis 不需要学习复杂的配置,简单易上手。
灵活性: MyBatis 允许使用原生 SQL、存储过程和高级映射。
动态 SQL: MyBatis 提供了强大的动态 SQL 支持,可以根据不同的条件生成不同的 SQL。
自动映射: MyBatis 提供了自动将查询结果映射到 Java 对象的功能,无需手动设置映射规则。
与 Spring 和其他框架集成: MyBatis 易于与 Spring 等框架集成,可以方便地用于各种 Java 项目。
缓存支持: MyBatis 提供了一级缓存和二级缓存的支持,可以有效地提高查询性能。
可插拔的: MyBatis 的设计允许开发者编写自定义的插件来扩展框架功能。
不需要使用 DAO 接口: 在 MyBatis 中,可以直接使用映射文件中配置的 SQL,而不需要创建繁琐的 DAO 接口。
JDBC(Java Database Connectivity)是 Java 中用于与数据库进行交互的标准接口。尽管 JDBC 是一个强大的工具,但它也有一些局限性:
冗长的代码: 使用 JDBC 进行数据库操作需要编写很多冗长的代码,包括数据库连接的建立和关闭、异常处理、SQL 语句的构建等。这些代码量较大,容易出错,降低了开发效率。
手动处理异常: 在 JDBC 中,开发者需要手动处理 SQL 异常,这意味着在每次数据库操作时都需要编写大量的异常处理代码,增加了代码的复杂性。
硬编码的 SQL 语句: 在 JDBC 中,SQL 语句通常是硬编码在 Java 代码中的,这样的做法不利于代码的维护和修改。如果需要修改 SQL 语句,开发者需要修改 Java 代码,这样的耦合性较高。
性能: JDBC 的性能通常较低,特别是在频繁进行数据库操作的时候。每次数据库操作都需要建立连接、执行 SQL 语句、关闭连接,这些操作会增加数据库的负担。
缺乏对象关系映射(ORM): JDBC 是一种底层的数据库操作接口,它不提供对象关系映射的功能。这意味着开发者需要手动将数据库查询结果映射到 Java 对象,增加了开发的复杂性。
事务管理: 在 JDBC 中,事务管理需要手动编写代码来处理,包括事务的开始、提交、回滚等操作。这些操作容易出错,并且增加了代码的复杂性。
为了解决这些问题,很多 Java 开发者转向了使用持久层框架,如Hibernate、MyBatis 等。这些框架提供了更高级、更方便的数据库操作方式,能够简化开发流程,提高开发效率。使用这些框架,开发者可以更专注于业务逻辑的实现,而不是花费大量的时间在处理数据库操作上。
MyBatis是一个基于Java的持久化框架,它的底层原理主要围绕着数据库访问和SQL映射展开。以下是MyBatis框架底层原理的主要组成部分:
配置文件(mybatis-config.xml):
MyBatis的配置文件包含了框架的核心配置信息,包括数据源、事务管理、缓存配置等。它定义了框架的全局行为。
映射文件(Mapper.xml):
映射文件是MyBatis的核心组成部分,它包含了SQL语句的定义以及这些SQL语句与Java方法之间的映射关系。在映射文件中,可以定义SQL查询、插入、更新、删除等操作,以及它们的输入参数和输出结果的映射。
SqlSessionFactory:
SqlSessionFactory 是 MyBatis 的核心接口,它负责创建 SqlSession 实例。SqlSessionFactory 的实现类 DefaultSqlSessionFactory 负责解析配置文件,构建并管理 SqlSession 的生命周期。
SqlSession:
SqlSession 是 MyBatis 框架中用于执行 SQL 操作的主要接口。它提供了各种查询、插入、更新、删除等方法,以及提交事务、关闭资源等功能。
Executor:
Executor 负责 SQL 的执行,它会将映射文件中定义的 SQL 语句解析成数据库可以执行的语句,执行SQL 并返回结果。
StatementHandler、ParameterHandler、ResultSetHandler、TypeHandler:
这些组件负责具体的 SQL 语句处理,比如 StatementHandler 负责 PreparedStatement 的创建和参数设置,ParameterHandler 负责 SQL 参数的处理,ResultSetHandler 负责结果集的处理,TypeHandler 负责 Java 类型与数据库类型之间的转换。
在 MyBatis 的工作流程中,SqlSessionFactory 负责初始化并加载配置文件,创建 SqlSession 实例。SqlSession 通过映射文件执行 SQL 语句,调用 Executor 执行具体的数据库操作。执行结果由ResultSetHandler 处理,最终返回给调用者。
MyBatis 的工作流程图:
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING" />
settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis"/>
<property name="username" value="root"/>
<property name="password" value="kdx010908"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="com/kdx/entity/User.xml"/>
<mapper resource="com/kdx/mapper/UserMapper.xml"/>
mappers>
configuration>
User.java
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
public User() {
}
public User(String username) {
this.username = username;
}
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 + '\'' +
'}';
}
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test">
<select id="findUserById" resultType="com.kdx.entity.User" parameterType="int">
select * from user where id = #{id}
select>
<insert id="addUser" parameterType="com.kdx.entity.User">
INSERT INTO USER (username) VALUES (#{username})
insert>
<update id="updateUser" parameterType="int">
UPDATE USER SET username = #{username} WHERE id = #{id}
update>
<delete id="deleteUser" parameterType="int">
DELETE FROM USER WHERE id = #{id}
delete>
mapper>
UserTest .java
public class UserTest {
@Test
public void findUserById() throws Exception{
String resource = "SqlMapConfig.xml";
//读配置文件
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
//创建会话工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//打开会话
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = sqlSession.selectOne("test.findUserById",1);
System.out.println(user);
//关闭会话
sqlSession.close();
}
@Test
public void addUser() throws Exception{
String resource = "SqlMapConfig.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.insert("test.addUser",new User("kdx"));
sqlSession.commit();
System.out.println("ok");
sqlSession.close();
}
@Test
public void updateUser() throws Exception{
String resource = "SqlMapConfig.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.update("test.updateUser",16);
sqlSession.commit();
System.out.println("ok");
sqlSession.close();
}
@Test
public void deleteUser() throws Exception{
String resource = "SqlMapConfig.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.delete("test.deleteUser",37);
sqlSession.commit();
System.out.println("ok");
sqlSession.close();
}
}
public interface UserDao {
//根据id查询用户
public User findUserById(Integer id)throws Exception;
}
public class UserDaoImpl implements UserDao {
private SqlSessionFactory sqlSessionFactory;
public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public User findUserById(Integer id) throws Exception {
//会话
SqlSession sqlSession = sqlSessionFactory.openSession();
User user=sqlSession.selectOne("test.findUserById",id);
sqlSession.close();
return user;
}
}
public class UserDaoTest {
SqlSessionFactory factory =null;
@Before
public void beforeMethod() throws IOException {
String resource="SqlMapConfig.xml";
//解析
InputStream inputStream = Resources.getResourceAsStream(resource);
// 构建 sqlSessionFactory
factory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testFind() throws Exception {
//创建 UserDao的对象
UserDao userDao = new UserDaoImpl(factory);
User user = userDao.findUserById(1);
System.out.println(user);
}
}
1.在 mapper.xml 中 namespace 为 mapper 接口地址
<mapper namespace="com.kdx.mapper.UserMapper">mapper>
2.在 mapper.xml 中 statementID 要和 mapper 接口中的方法名保持一致
例如:UserMapper .xml中的
<select id="findAll" resultType="com.kdx.entity.User">
select id,username,birthday,sex,address from user
select>
对应 UserMapper 接口中的
public List<User> findAll();
3.在 mapper.xml 中的 resultType 要和 mapper.java 中输出参数类型保持一致[返回值]
例如上面例子中 resultType 和 User 对应
4.在 mapper.xml 中的 parameterType 要和 mapper.java 中输入参数类型保持一致[形参]
例如:UserMapper .xml中的 parameterType
<delete id="delByUserId" parameterType="int">
DELETE FROM USER WHERE id = #{id}
delete>
对应 UserMapper 接口中的 int
public void delByUserId(int id);
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kdx.mapper.UserMapper">
<resultMap id="UserResultMap" type="com.kdx.entity.User">
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="birthday" property="birthday"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
resultMap>
<select id="findAll" resultMap="UserResultMap">
select id user_id,username,birthday,sex,address from user
select>
<insert id="addUser">
INSERT INTO USER (username) VALUES (#{username})
insert>
<update id="updateUser">
update USER
<set>
<if test="username != null and username != ''">
username = #{username}
if>
set>
where id = #{id}
update>
<delete id="delByUserId" parameterType="int">
DELETE FROM USER WHERE id = #{id}
delete>
mapper>
public interface UserMapper {
List<User> findAll();
void addUser(User user);
void updateUser(User user);
void delByUserId(Integer id);
}
public class UserMapperTest {
private SqlSessionFactory factory = null;
private SqlSession sqlSession = null;
@Before
public void before() throws IOException {
String resource = "SqlMapConfig.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
sqlSession = factory.openSession();
}
@Test
public void findAll() {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.findAll();
for (User user : userList) {
System.out.println(user);
}
}
@Test
public void addUser() {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.addUser(new User("hhh"));
sqlSession.commit();
System.out.println("ok");
sqlSession.close();
}
@Test
public void updateUser(){
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUsername("kdx");
user.setId(36);
userMapper.updateUser(user);
sqlSession.commit();
System.out.println("ok");
sqlSession.close();
}
@Test
public void delByUserId(){
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.delByUserId(40);
sqlSession.commit();
System.out.println("ok");
sqlSession.close();
}
}
MyBatis框架提供了一种称为动态SQL的功能,允许根据不同的条件动态构建SQL语句。这在实际应用中非常有用,因为它可以帮助你避免编写大量重复的SQL语句,提高了SQL语句的可维护性和灵活性。
参考网址:https://mybatis.net.cn/dynamic-sql.html
以下是MyBatis中动态SQL的两种常见用法:
1.if 元素
<select id="findUserList" parameterType="com.kdx.vo.UserQueryVo" resultType="com.kdx.entity.UserCustom">
select * from user
<where>
<if test="userCustom != null">
<if test="userCustom.username != null and userCustom.username != ''">
and user.username like '%${userCustom.username}%'
if>
<if test="userCustom.sex != null and userCustom.sex != ''">
and user.sex = #{userCustom.sex}
if>
if>
where>
select>
以上示例中,在查询用户时,若传入用户的名字则在 sql 语句中拼接 and user.username like '%${userCustom.username}%'
,若传入用户的性别则在 sql 语句中拼接 and user.sex = #{userCustom.sex}
标签相当于 sql 语句中的 where 1=1
2.foreach 元素:对集合进行遍历(尤其是在构建 IN 条件语句的时候)
标签中各个属性解释如下:
例如查询 id 为1,10,15用户的信息,sql语句有两种写法
若为:
SELECT * FROM USER WHERE id = 1 OR id = 10 OR id =15
xml 文件如下
<foreach collection="ids" item="user_id" open="and (" close=")" separator="or">
id = #{user_id}
foreach>
若为:
SELECT * FROM USER WHERE id IN (1,10,15)
xml 文件如下
<foreach collection="ids" item="user_id" open="and id in(" close=")" separator=",">
#{user_id}
foreach>
SQL 片段是 MyBatis 中的一种重用 SQL代码 的方法。它能够将一部分 SQL 代码定义成一个可重用的片段,然后在需要的地方进行引用,避免了代码的重复。SQL 片段通常定义在 MyBatis 的XML配置文件中,可以通过
标签来创建。
例如:
<sql id="findUserNameSex">
<if test="userCustom != null">
<if test="userCustom.username != null and userCustom.username != ''">
and user.username like '%${userCustom.username}%'
if>
<if test="userCustom.sex != null and userCustom.sex != ''">
and user.sex = #{userCustom.sex}
if>
if>
sql>
然后,在其他地方的 SQL 语句中,可以通过
标签来引用这个 SQL 片段:
<select id="findUserList" parameterType="com.kdx.vo.UserQueryVo" resultType="com.kdx.entity.UserCustom">
select * from user
<where>
<include refid="findUserNameSex"/>
where>
select>
#{}
是预编译处理, ${}
是字符串替换。
Mybatis 在处理#{}
时,会将 sql 中的#{}
替换为?
号,调用 PreparedStatement 的 set 方法来赋值;
Mybatis 在处理${}
时,就是把${}
替换成变量的值。这样可能会存在SQL注入的风险。因为参数值会被直接拼接到SQL语句中,不会被预编译,可能导致安全问题。而使用#{}
可以有效的防止 SQL 注入,提高系统安全性。
例如有四张表,user、orders、orderdetail、items,它们的关系如下:
user -->
orders:一个用户可以创建多个订单,一对多
orders -->
user: 一个订单只能由一个用户创建,一对一
orders -->
orderdetail:一个订单可以有多个订单明细,一对多
orderdetail -->
orders:一个订单明细只能属于一个订单,一对一
orderdetail -->
items:一个订单明细中只能有一个商品,一对一
items -->
orderdetail: 一个商品可以出现在多个订单明细中,一对多
需求: 查询订单,关联查询创建订单的用户信息
思路:
确定查询主表:订单表
确定查询关联表: 用户表
使用内连接
确定SQL:
SELECT orders.*,user.`username`,user.`sex`,user.`address`
FROM USER,orders
WHERE user.`id`=orders.`user_id`
订单实体类 Orders:
public class Orders implements Serializable {
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
private User user;
public Orders() {
}
public Orders(Integer userId, String number, Date createtime) {
this.userId = userId;
this.number = number;
this.createtime = createtime;
}
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 getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public Date getCreatetime() {
return createtime;
}
public void setCreatetime(Date createtime) {
this.createtime = createtime;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
@Override
public String toString() {
return "Orders{" +
"id=" + id +
", userId=" + userId +
", number='" + number + '\'' +
", createtime=" + createtime +
", note='" + note + '\'' +
", user=" + user +
'}';
}
}
使用定制类OrdersCustom接收:
public class OrdersCustom extends Orders{
private String username;
private String sex;
private String address;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
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;
}
}
UserMapper接口:
public interface UserMapper {
//查询用户及订单
//resultType
List<OrdersCustom> findOrderAndUser();
//resultMap
List<OrdersCustom> findOrderAndUserResultMap();
}
UserMapper.xml
若用 resultType
<select id="findOrderAndUser" resultType="com.kdx.entity.OrdersCustom">
SELECT orders.*,user.`username`,user.`sex`,user.`address`
FROM USER,orders
WHERE orders.`user_id`=user.`id`
select>
若用 resultMap
<select id="findOrderAndUserResultMap" resultMap="findOrderAndUserMap">
SELECT orders.*,user.`username`,user.`sex`,user.`address`
FROM USER,orders
WHERE orders.`user_id`=user.`id`
select>
<resultMap id="findOrderAndUserMap" type="com.kdx.entity.OrdersCustom">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<association property="user" javaType="com.kdx.entity.User">
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
association>
resultMap>
resultType 和 resultMap 总结
resultType 使用比较简单,如果pojo中的属性和数据库中的列名完全一致, 即可以使用它完成映射。没有特殊要求的查询使用 resultType 完成映射
resultMap 需要单独写 resultMap,有点儿小麻烦,如果对查询有特殊要求【比如添加user属性】 用resultMap 完成映射
resultMap可以实现延迟加载,resultType无法实现延迟加载。
需求:查询订单以及订单明细
思路:
确定查询主表:订单表
确定查询关联表: 订单明细表
使用内连接
确定SQL:
SELECT orders.*,
user.`username`,user.`sex`,user.`address`,
orderdetail.`items_id`,orderdetail.`items_num`,orderdetail.`orders_id`
FROM USER,orders,orderdetail
WHERE user.`id`=orders.`user_id` AND orders.`id`=orderdetail.`orders_id`
订单明细实体类 OrderDetail:
public class OrderDetail implements Serializable {
private Integer id;
private Integer ordersId;
private Integer itemsId;
private Integer itemsNum;
public OrderDetail() {
}
public OrderDetail(Integer ordersId, Integer itemsId) {
this.ordersId = ordersId;
this.itemsId = itemsId;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getOrdersId() {
return ordersId;
}
public void setOrdersId(Integer ordersId) {
this.ordersId = ordersId;
}
public Integer getItemsId() {
return itemsId;
}
public void setItemsId(Integer itemsId) {
this.itemsId = itemsId;
}
public Integer getItemsNum() {
return itemsNum;
}
public void setItemsNum(Integer itemsNum) {
this.itemsNum = itemsNum;
}
@Override
public String toString() {
return "OrderDetail{" +
"id=" + id +
", ordersId=" + ordersId +
", itemsId=" + itemsId +
", itemsNum=" + itemsNum +
'}';
}
}
在 用户实体类 User 中加入:
//订单
private List<Orders> ordersList = new ArrayList<>();
public List<Orders> getOrdersList() {
return ordersList;
}
public void setOrdersList(List<Orders> ordersList) {
this.ordersList = ordersList;
}
在 订单实体类 Orders 中加入:
//订单明细
private List<OrderDetail> orderdetails = new ArrayList<>();
public List<OrderDetail> getOrderDetailList() {
return orderdetails;
}
public void setOrderDetailList(List<OrderDetail> orderdetails) {
this.orderdetails = orderdetails;
}
在 UserMapper接口 加入:
//查询订单及订单明细
List<OrderDetail> findOrderAndUserAndOrderDetail();
UserMapper.xml:
<select id="findOrderAndUserAndOrderDetail" resultMap="findOrderAndUserAndOrderDetailResultMap">
SELECT orders.*,
user.`username`,user.`sex`,user.`address`,
orderdetail.`items_id`,orderdetail.`items_num`,orderdetail.`orders_id`
FROM USER,orders,orderdetail
WHERE user.`id`=orders.`user_id` AND orders.`id`=orderdetail.`orders_id`
select>
<resultMap id="findOrderAndUserAndOrderDetailResultMap" type="com.kdx.entity.Orders" extends="findOrderAndUserMap">
<collection property="orderdetails" ofType="com.kdx.entity.OrderDetail">
<id column="orderdetail_id" property="id"/>
<result column="orders_id" property="ordersId"/>
<result column="items_id" property="itemsId"/>
<result column="items_num" property="itemsNum"/>
collection>
resultMap>
需求: 查询用户及用户购买的商品
思路:
确定查询主表: 用户表
确定查询关联表:由于用户和商品没有直接关联,所以通过订单表、订单明细表以及商品表进行关联
使用内连接
确定SQL:
SELECT orders.*,
user.`username`,user.`sex`,user.`address`,orderdetail.`id` orderdetail_id,
orderdetail.`items_id`,orderdetail.`items_num`,orderdetail.`orders_id`,
items.`name` item_name,items.`price` item_price,items.`detail` item_detail
FROM USER,orders,orderdetail,items
WHERE user.`id`=orders.`user_id` AND orders.`id`=orderdetail.`orders_id` AND orderdetail.`items_id`=items.`id`
由于在一对多的例子中已经做了许多工作,在此不再重复
商品实体类 Items:
public class Items implements Serializable {
private Integer id;
private String name;
private Double price;
private String detail;
private String pic;
private Date createtime;
public Items() {
}
public Items(Integer id, String name, Double price, String detail, String pic, Date createtime) {
this.id = id;
this.name = name;
this.price = price;
this.detail = detail;
this.pic = pic;
this.createtime = createtime;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
public String getPic() {
return pic;
}
public void setPic(String pic) {
this.pic = pic;
}
public Date getCreatetime() {
return createtime;
}
public void setCreatetime(Date createtime) {
this.createtime = createtime;
}
@Override
public String toString() {
return "Items{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
", detail='" + detail + '\'' +
", pic='" + pic + '\'' +
", createtime=" + createtime +
'}';
}
}
在订单明细实体类 OrderDetail 中加入:
//商品
private Items items;
public Items getItems() {
return items;
}
public void setItems(Items items) {
this.items = items;
}
在 UserMapper接口 加入:
//查询用户及其购买的商品
List<User> findUserAndItemResultMap();
UserMapper.xml:
<select id="findUserAndItemResultMap" resultMap="findUserItemResultMap">
SELECT orders.*,
user.`username`,user.`sex`,user.`address`,orderdetail.`id` orderdetail_id,
orderdetail.`items_id`,orderdetail.`items_num`,orderdetail.`orders_id`,
items.`name` item_name,items.`price` item_price,items.`detail` item_detail
FROM USER,orders,orderdetail,items
WHERE user.`id`=orders.`user_id` AND orders.`id`=orderdetail.`orders_id` AND orderdetail.`items_id`=items.`id`
select>
<resultMap id="findUserItemResultMap" type="com.kdx.entity.User">
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="birthday" property="birthday"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
<collection property="ordersList" ofType="com.kdx.entity.Orders">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<collection property="orderdetails" ofType="com.kdx.entity.OrderDetail">
<id column="orderdetail_id" property="id"/>
<result column="orders_id" property="ordersId"/>
<result column="items_id" property="itemsId"/>
<result column="items_num" property="itemsNum"/>
<association property="items" javaType="com.kdx.entity.Items">
<id column="items_id" property="id"/>
<result column="item_name" property="name"/>
<result column="item_price" property="price"/>
<result column="item_detail" property="detail"/>
association>
collection>
collection>
resultMap>
MyBatis 中的延迟加载(Lazy Loading,又叫懒加载、慢加载)是一种性能优化技术,它可以在需要的时候才从数据库中加载数据,而不是在对象初始化时就立即加载。这样可以避免在查询主对象时同时加载关联对象,提高了查询性能和减少了数据库查询次数,也就是说可以按需查询数据库。
延迟加载只能使用
来实现,
都具备延迟加载功能。
、
示例:查询订单表关联查询用户信息
1.全局配置文件 SqlMapConfig.xml 中加入以下内容
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
settings>
2.在UserMapper.xml 中加入以下内容
<select id="findUserById" resultType="com.kdx.entity.User" parameterType="int">
select * from user where id = #{id}
select>
<select id="findOrdersAndUserLazyLoading" resultMap="lazyLoading">
select * from orders
select>
<resultMap id="lazyLoading" type="com.kdx.entity.Orders">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<association property="user" javaType="com.kdx.entity.User" select="findUserById" column="user_id">
association>
resultMap>
3.在UserMapper 接口中加入以下内容
//根据id查询用户
User findUserById(int id);
//延迟加载查询订单及用户信息
List<Orders> findOrdersAndUserLazyLoading();
4.测试延迟加载
public class UserMapperTest {
private SqlSessionFactory factory = null;
private SqlSession sqlSession = null;
//在JUnit测试中,@Before 注解标记的方法会在每个测试方法运行之前被执行。
@Before
public void before() throws IOException {
String resource = "SqlMapConfig.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
sqlSession = factory.openSession();
}
//测试延迟加载
@Test
public void findOrdersAndUserLazyLoading(){
sqlSession = factory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<Orders> ordersList = userMapper.findOrdersAndUserLazyLoading();
for (int i = 0; i < ordersList.size(); i++) {
System.out.println("------------------------------");
Orders orders = ordersList.get(i);
User user = orders.getUser();
}
}
}
MyBatis提供了两级缓存机制来提高性能:一级缓存(本地缓存)和二级缓存(全局缓存)
一级缓存是指在同一个 SqlSession 中进行的缓存。默认情况下,MyBatis 会开启一级缓存。
当执行一个查询时,查询的结果会被放入 SqlSession 的缓存中,如果后续的查询使用相同的 SqlSession 对象并且参数也相同,MyBatis 会直接从缓存中取得结果,而不会再次查询数据库。
一级缓存的生命周期是和 SqlSession 绑定的,当 SqlSession 关闭时,缓存数据也会被清空。
测试一级缓存:
public class UserMapperTest {
private SqlSessionFactory factory = null;
private SqlSession sqlSession = null;
//在JUnit测试中,@Before 注解标记的方法会在每个测试方法运行之前被执行。
@Before
public void before() throws IOException {
String resource = "SqlMapConfig.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
sqlSession = factory.openSession();
}
//测试一级缓存
@Test
public void testCache1(){
sqlSession = factory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user1 = userMapper.findUserById(1);
System.out.println("-------------------------");
User user2 = userMapper.findUserById(1);
System.out.println(user1==user2);
sqlSession.close();
}
}
在查询用户信息之前打断点进行调试,调试结果如下:
从调试结果来看,第一次查询用户信息时,通过数据库查询数据,第二次查询用户信息时则从一级缓存中查询到了用户信息,并且两次查询得到的用户哈希地址也相同。
二级缓存是指在同一个 Mapper 的不同 SqlSession 之间进行的缓存。开启二级缓存需要在Mapper的映射文件中进行配置:(在UserMapper.xml进行如下配置)
<mapper namespace="com.example.mapper.UserMapper" >
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
mapper>
在MyBatis的配置文件中,也需要开启全局缓存:(在全局配置文件 SqlMapConfig.xml 中加入以下内容)
<settings>
<setting name="cacheEnabled" value="true"/>
settings>
开启二级缓存后,当一个 SqlSession 执行完毕并提交或关闭时,这个 Session 中的所有数据会被提交到二级缓存中。当另一个 SqlSession 需要执行相同的查询时,MyBatis 会先从二级缓存中查找是否有相应的数据,如果有,就直接返回,不再查询数据库。
注意:为了使得某个 Mapper 的某个查询开启二级缓存,该 Mapper 的对应的 POJO 需要实现序列化接口(Serializable),因为二级缓存的数据可能需要被序列化到磁盘中,以便在应用重启后能够重新加载。
测试二级缓存:
public class UserMapperTest {
private SqlSessionFactory factory = null;
//在JUnit测试中,@Before 注解标记的方法会在每个测试方法运行之前被执行。
@Before
public void before() throws IOException {
String resource = "SqlMapConfig.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
}
//测试二级缓存
@Test
public void testCache2(){
SqlSession sqlSession1 = factory.openSession();
SqlSession sqlSession2 = factory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.findUserById(1);
sqlSession1.close();//需要关闭sqlSession1后再查询
System.out.println("---------------------------------");
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.findUserById(1);
System.out.println(user1==user2);
sqlSession2.close();
}
}
在查询用户信息之前打断点进行调试,调试结果如下:
从调试结果来看,第一次查询用户信息时,由于缓存中没有相应数据,所以需要通过数据库查询数据;第二次查询用户信息时,由于 sqlSession1 执行完毕并关闭,MyBatis 会先从二级缓存中查找是否有相应的数据,如果有,就直接返回,不再查询数据库。并且两次查询得到的用户哈希地址也相同。