MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息;
- 持久化:将瞬时状态的数据转换为持久状态数据的过程称为持久化过程,持久化是将程序数据在持久状态和瞬时状态间转换的机制。如:文件IO,JDBC,ORM框架;
- ORM框架:对象关系映射,通过 Java 对象与数据库关系表的映射实现数据的持久化,在操作
数据库时需要使用 SQL 语句并对结果进行封装,而 ORM 框架要达到的效果是简化数 据库的开发,让数据库操作的过程更加自动化,例如:Hibernate,MyBatis - Hibernate:一个完全的ORM框架,在Hibernate中可以不编写任何sql语句实现对数据库的操作,但是优化sql比较麻烦,学习成本较高,目前的使用集中在需求变化不大的场景;
- MyBatis:不完全的ORM框架,需要自己编写SQL语句来实现数据库的操作,因此对于sql的优化就变得简单了许多,适合于需求变化较大的应用场景,并且学习成本不高,易上手;
二,MyBatis的相关详细配置
- 首先建立可设置Maven依赖的项目;
- 这里Maven依赖需要自己配置本地maven库,教程:maven本地库搭建流程,并配置setting.xml文件;(就是为了可以引入maven依赖)
- 在pom.xml文件的
<dependencies> 标签种引入四种maven依赖;(Maven官网)
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
- 建立如下目录
- mybatis-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">
<configuration>
<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://localhost:3306/myshopping"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/需要映射的对应的xml文件"/>
</mappers>
</configuration>
- userMapper.xml和ordersMapper.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="接口的包地址">
</mapper>
- log4j2的配置(后面sql语句和详细信息,配置该日志会得到详细显示)(直接拿走,可用!!!,当然也可以用自带的日志,下面讲配置)
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="com.mybatis.mapper" level="trace" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Root level="debug">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
不用log4j2日志也可以,默认的日志在mybatis-config.xml文件中全局配置
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
三,MyBatis的简单使用
使用说明
- 建立数据库,在myshopping数据库下建tbl_user(主键:user_id)和tbl_orders(主键:orders_id)表,两个表的外键user_id(很重要!后面的一对多,多对一映射需要,还有表连接)
- 在dao包下的User和Orders实体类的属性分别为下,getter,setter,toString方法自己生成
private Integer userId;
private String userName;
private String userPassword;
private String userEmail;
private Date userBirthday;
private String userHobbys;
private Integer userSex;
private String userAddress;
private String orderId;
private String orderToName;
private String orderToAddress;
private String orderToPhone;
private Date orderTime;
private Integer orderStatus;
private Double orderTotalPrice;
private Integer userId;
- 在UserMapper接口下写方法;(注意:不实现,MyBatis内有很多方法帮我们实现,我们要做的就是配置路径)
List<User> queryAllUser();
- 譬如:这个方法queryAllUser,那么你的userMapper.xml文件下的mapper标签的属性namespace=“com.mybatis.mapper.UserMapper”,注意这里是接口的包地址
<mapper namespace="com.mybatis.mapper.UserMapper">
</mapper>
- 此时:你的mybatis-config.xml文件下的mappers标签下的mapper标签的 属性resource=“mapper/userMapper.xml”,这里是根地址
<mapper resource="mapper/userMapper.xml"/>
- 然后在userMapper.xml文件下的mapper标签内写入select标签,注意id属性名为方法名resultType="com.mybatis.model.User"是返回的实体类
<mapper namespace="com.mybatis.mapper.UserMapper">
<select id="queryAllUser" resultType="com.mybatis.model.User">
select * from tbl_user
</select>
</mapper>
- 使用mapper代理对象的前提条件:
- 第一点:userMapper.xml文件中的 namespace 必须为 GoodsMapper 接口的完整路
径, 第二点:UserMapper 接口中的方法名必须和 goodsMapper.xml 中 SQL 操作标签的 id 名对应一致 - 然后直接进入测试中
public class Test {
public static void main(String[] args) throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.queryAllUser();
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
}
日志输出
得到结果:(这就是日志输出的强大,打印很耗资源,日志输出减少资源消耗,并且,,可以记录操作(涉及到文件输出,文件内我写了,但是注释了,想用的小伙伴可以尝试一下))
单元测试
- 建立此UserTest类
- 方法体不变,添加
@Test 注解(必须要有maven依赖,上面添加过了) 结果与上面正常测试一致(别杠没用,当你方法多的时候,挨个调用显得很麻烦)
映射传参
- 当在UserMapper接口下的我的方法有参数时,需要用
@Param 注解
void updateGoods(@Param("userName") String name, @Param("userId")int id);
- 在对应的userMapper.xml中添加update标签(update不需要返回类型,所以不用设置resultType)
<update id="updateGoods">
update tbl_goods set userName=#{userName} where userId=#{userId}
</update>
- 映射传参,就需要采用注解的方式(达到一一对应的效果)
- #{}:生成的是带有问号占位符的 SQL 语句
- ${}:生成的是使用连接字符串拼接而成的 SQL 语句
(因此可能会产生sql注入,两者都是在日志中的生成方式的区别勤奋的小伙伴自己测试一下) - 此时执行单元测试(注意修改数据库的语句要进行手动提交,即
sqlSession.commit(); ,否则数据达不到我们的想要效果)
@Test
public void queryUser() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.updateGoods("小白",96);
sqlSession.commit();
sqlSession.close();
}
四,MyBatis的结果映射
即为返回类型在增删改查的标签中使用例:
<select id="queryAllUser" resultType="com.mybatis.model.User">
select * from tbl_user
</select>
resultType:可以指定基本类型,如 int,double,string 等也可以指定为自定义对象类型,但要求自定义对象中的属性和数据库查询的列名一致 resultMap:属于高级映射,当 Java 对象中的属性名和数据库中的列名不一致时,就需要使用 resultMap 来将数据库中的列映射到 Java 对象中的属性resultMap 可以继承 resultMap(继承配置),当对象中的属性和表中的字段名一致时可以使用 autoMapping=“true”实现自动映射,分为一对多,多对一,等
结果映射resultType
- 举例查询userId的数量
int count();
<select id="count" resultType="int">
SELECT COUNT(userId) FROM tbl_user
</select>
resultMap结果映射一对多
描述:一对多:(一个用户对应多个订单,建立在两个表之间存在外键的情况)通过用户查询用户的所有订单
- 在model包下的实体类User中添加Orders的List集合(即在一方添加多方)
List<Orders> ordersList;
public List<Orders> getOrdersList() {
return ordersList;
}
public void setOrdersList(List<Orders> ordersList) {
this.ordersList = ordersList;
}
- 在接口中写入方法
User queryUserAndOrder(int id);
- 在userMapper.xml文件中写入如下查询和映射,通过id关联
<resultMap id="userOrderMap" type="com.mybatis.model.User" autoMapping="true">
<collection property="ordersList"
column="userId"
ofType="com.mybatis.model.Orders"
select="com.mybatis.mapper.OrdersMapper.queryOrdersByUser"
fetchType="lazy">
</collection>
</resultMap>
<select id="queryUserAndOrderByUser" resultMap="userOrderMap">
select * from tbl_user where userid=#{userid}
</select>
- 再配置它的orderMapper.xml文件(namespace,select标签)根据上面的resultMap标签下collection标签的select属性的地址直接调用sql语句,无需方法(测试过了)
<mapper namespace="com.mybatis.mapper.OrdersMapper">
<select id="queryOrdersByUser" resultType="com.mybatis.model.Orders">
select * from tbl_order where userid=#{userid}
</select>
</mapper>
- 别忘记在核心配置类mybatis-config.xml中mappers标签中添加配置SQL映射文件
<mapper resource="mapper/ordersMapper.xml"/>
- 从而达到用户查询订单信息的目的,即为一对多!(懒加载可以实现调用才查询,自己测试在resultMap标签内可配置)
resultMap结果映射多对一
描述:多对一:(多个订单对一个用户,建立在两个表之间存在外键的情况)通过订单查询用户信息
- 在model包下的实体类Orders中添加User对象(即在多方添加一方)
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
- 在接口中写入方法
Orders queryUserAndOrdersByOrders(String id);
- 在ordersMapper.xml文件中写入如下查询和映射,通过id关联
<resultMap id="orderUserMap" type="com.mybatis.model.Orders" autoMapping="true">
<association property="user"
column="userId"
select="com.mybatis.mapper.UserMapper.queryUserAndOrderByOrders"></association>
</resultMap>
<select id="queryUserAndOrdersByOrders" resultMap="orderUserMap">
select * from tbl_order where orderId=#{orderId}
</select>
- 再配置它的userMapper.xml文件(namespace,select标签)根据上面的resultMap标签下association标签的select属性的地址直接调用sql语句,同样无需方法(测试过了)
<select id="queryUserAndOrderByOrders" resultType="com.mybatis.model.User">
select * from tbl_user where userid=#{userid}
</select>
- 单元测试
@Test
public void queryUser() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
OrdersMapper mapper = sqlSession.getMapper(OrdersMapper.class);
Orders orders = mapper.queryUserAndOrdersByOrders("20210420222728240");
sqlSession.commit();
sqlSession.close();
}
- 从而达到用户查询订单信息的目的,即为多对一!(懒加载可以实现调用才查询,自己测试在resultMap标签内可配置)
表连接查询
描述:表连接建立在两个表之间存在外键的情况,通过orderid查询user信息
- 在OrdersMapper接口下写入方法
Orders queryOrderById(String id);
- 在ordersMapper.xml文件下写入
<resultMap id="orderUserConnMap" type="com.mybatis.model.Orders" autoMapping="true">
<association property="user" column="userId" autoMapping="true">
</association>
</resultMap>
<select id="queryOrderById" resultMap="orderUserConnMap">
SELECT
u.*,o.*
FROM
tbl_user u
INNER JOIN
tbl_order o
ON
u.userId=o.userId
WHERE
o.orderId=#{orderId}
</select>
- 调用相关方法实现表连接查询
五,MyBatis的重要机制
MyBatis的加载机制
MyBatis的加载机制分为:
- 立即加载:通过一对多可以看出,当没有设置懒加载的情况,SQL语句中的查询内容都会在日志中显示出来,这便是立即加载不设置fetchtype属性默认为立即加载eager
- 预先抓取:使用表连接查询对象及其关联对象的方式,将虚表内容全部加载(小编感觉可以归为立即加载)
- 延迟加载:(懒加载)是一种按需加载方式,即调用才加载,不调用不加载,懒加载需要设置fetchtype属性为 lazy
延迟加载(lazy load)是(也称为懒加载)延迟加载机制是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作,以达到减少数据库压力的作用。
懒加载的全局设置
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
MyBatis的缓存机制
在 MyBatis 中为提高查询效率,降低数据库的负担,MyBatis 提供缓存机制(内部是map集合,map(com.mybatis.model.Orders#查询的数据,查询的结果)),通过缓存机制降低与数据库的交互次数,从而缓解数据库压力,达到提高性能的作用 MyBatis 的缓存有两种:
- 一级缓存(sqlSession 级缓存):在同一个 sqlSession 中起作用,sqlSession 一旦关闭,一级缓存消失;
- 二级缓存(mapper 级缓存):在同一个 mapper 中多个 sqlSession 可以共享的缓存,需要配置和序列化
当查询一个对象时,会先在二级缓存中查找(该对象允许存入到二级缓存中),如果找到则直接返回,如果找不到则在到一级缓存中查找,如果找到则返回,如果找不到会向数据库发送查询请求,并查询相应的数据,将该数据存入到一级缓存和二级缓存(该对象允许存入到二级缓存中),以备使用
一级缓存:(MyBatis默认开启一级缓存,一个sqlSession的生命周期,close就完了,所以又叫sqlSession 级缓存)
描述:用上面的表连接进行测试,
操作:当我对一个信息查询两次的时候,
第一次查询从一级缓存找,没有,然后查数据库
而第二次,从一级缓存找,找到了,直接用这便是一级缓存
一级缓存测试: 一级缓存结果: 手动清理一级缓存: 手动清理一级缓存结果: 二级缓存:(存到了SqlSessionFactory里)
>>官方提示:
二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,
但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
>>官方解释:
映射语句文件中的所有 select 语句的结果将会被缓存。
映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
缓存不会定时进行刷新(也就是说,没有刷新间隔)。
缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改
- 全局设置(默认情况下二级缓存是开启但是具体的开启需要自己去配置,所以这里可以不配置)
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
- 当前映射文件配置该实体类使用二级缓存,在对应的mapper文件下添加cache标签
eviction 属性: LRU – 最近最少使用:移除最长时间不被使用的对象。 FIFO – 先进先出:按对象进入缓存的顺序来移除它们。 SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。 WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。 默认的清除策略是 LRU flushInterval (刷新间隔)属性: 属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。 readOnly (只读)属性:属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。 size (引用数目)属性: 属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。 - 当前操作对应的sql语句查询内容存入二级缓存,true开启
二级缓存测试: 在相应的xml文件下写入cache标签,(标签可以不设属性和值) 在单元测试进行测试(普通测试类也可以,还是那句话,单元测试是为了让你不用去main方法内挨个调用)
所以:实现二级缓存的实体类必须实现序列化Serializable,因为我的表连接设计User和Orders所以需要两个都实现序列化接口 得到结果: 说明二级缓存成功
六,MyBatis的动态SQL
MyBatis 的强大特性之一便是它的动态 SQL,通过 MyBatis 的动态 SQL 可以对SQL 语句进行灵活拼接操作 where,if标签
<select id="queryGoods" resultType="goods">
select * from tbl_goods
<where>
<if test="g_name!=null and g_name!=''">
g_name like concat('%',#{g_name},'%')
</if>
<if test="g_start_price!=null">
and g_price >=#{g_start_price}
</if>
<if test="g_end_price!=null">
and g_price <=#{g_end_price}
</if>
<if test="g_start_date">
and g_date >=#{g_start_date}
</if>
<if test="g_end_date">
and g_date <=#{g_end_date}
</if>
</where>
</select>
choose、when、otherwise标签
<select id="queryTwoChoseOne" resultType="goods">
select * from tbl_goods
<where>
<choose>
<when test="g_start_price!=null">
g_price >=#{g_start_price}
</when>
<otherwise>
g_price between 0 and 100
</otherwise>
</choose>
</where>
set标签
<update id="updateGoods">
update tbl_goods
<set>
<if test="g_name!=null and g_name!=''">
g_name=#{g_name},
</if>
<if test="g_price!=null">
g_price=#{g_price},
</if>
<if test="g_date!=null">
g_date=#{g_date}
</if>
</set>
<where>
g_id=#{g_id}
</where>
</update>
trim标签
<update id="updateGoods1">
update tbl_goods
<trim prefix="set" suffixOverrides=",">
<if test="g_name!=null and g_name!=''">
g_name=#{g_name},
</if>
<if test="g_price != null">
g_price=#{g_price},
</if>
<if test="g_date != null">
g_date=#{g_date},
</if>
</trim>
<trim prefix="where">
g_id=#{g_id}
</trim>
</update>
foreach标签
<delete id="delGoods">
<if test="g_ids.length!=0">
delete from tbl_goods
<where>
g_id in
<foreach collection="g_ids" open="(" close=")" separator="," item="g_id">
#{g_id}
</foreach>
</where>
</if>
</delete>
SQL 复用标签
<sql id="publicSql">
<if test="g_name!=null and g_id!=null">
select * from tbl_goods wherer g_name=#{g_name} where g_id=#{g_id}
</if>
</sql>
<select id="selectTest">
<include refid="publicSql"></include>
</select>
七,MyBatis注解使用
常用注解
@Insert:SQL 语句–插入 @Update:SQL 语句–更新 @Delete: SQL 语句–删除 @Select:SQL 语句–查询 @Results:–定义结果映射 @Result:–用于映射属性 @ResultMap:用于引用 Results 定义的结果映射 @one:等同于,用于多对一关联中映射单个对象 @Many: 等同于,用于一对多关联中映射多方
基于注解的单表查询语句
- 注解的使用省去了在类似于UserMapper.xml和OrdersMapper.xml文件中配置大量的标签
- 实现的方式变成在mybatis-config.xml文件下的mappers标签中配置SQL映射,name属性:SQL语句注解所存在的包地址(该包下的所有接口都可以映射SQL注解)
<mappers>
<package name="com.mybatis.mapper"/>
</mappers>
- 在mapper下新建NewUserMapper接口,并写方法queryById,添加注解
public interface NewUserMapper {
@Select("select * from tbl_user where userId=#{userId}")
User queryByIdNote(int userId);
}
-
测试 -
结果
基于动态SQL的注解(官方的)
@Update({"<script>",
"update Author",
" <set>",
" <if test='username != null'>username=#{username},</if>",
" <if test='password != null'>password=#{password},</if>",
" <if test='email != null'>email=#{email},</if>",
" <if test='bio != null'>bio=#{bio}</if>",
" </set>",
"where id=#{id}",
"</script>"})
void updateAuthorValues(Author author);
基于注解的带返回值的查询语句(新版一对多)
- 在NewUserMapper接口下写如下方法注解
@Results(id = "user",value = {
@Result(property = "ordersList",column = "userId",
many = @Many(select ="com.mybatis.mapper.NewOrdersMapper.queryUserAndOrdersByIdNoteSon"))
})
@Select("select * from tbl_user where userId=#{userId}")
User queryUserAndOrdersByIdNote(int userId);
- 在NewOrdersMapper接口写
@Select(value = "select * from tbl_order where userId=#{userId}")
List<Orders> queryUserAndOrdersByIdNoteSon(int userId);
- 测试
- 结果
|