Java学习-Mybatis
Mybatis
Maven依赖:
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
持久层
持久化就是将持久状态和瞬时状态转化的过程
数据库(JDBC),IO文件持久化
1、第一个Mybatis程序
1.1、基础配置
- 编写Mybatis的核心配置文件
<?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.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://;ocalhost:3306/smbms?serverTimezone=UTC&userSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="handhand"/>
</dataSource>
</environment>
</environments>
</configuration>
- 编写Mybatis工具类
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。例如:
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
1.2、编写代码
- 实体类
public class User {
private int id;
private String name;
private String password;
public User(int id, String name, String password) {
this.id = id;
this.name = name;
this.password = password;
}
public User() {
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
- Dao接口
public interface UserDao {
public List<User> getUserList();
}
- 接口实现类,由原来的UserDaoImpl转变为Mapper配置文件
<?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">
<!--命名空间,绑定一个Dao接口-->
<mapper namespace="com.darker.dao.UserDao">
<select id="getUserList" resultType="com.darker.pojo.User" >
select * from smbms.user
</select>
</mapper>
1.3测试
注意点:org.apache.ibatis.binding.BindingException: Type interface com.darker.dao.UserDao is not known to the MapperRegistry.
<!--每一个Mapper.xml都需要在Mybatis的核心配置文件中注册-->
<mappers>
<mapper resource="com/darker/dao/UserMapper.xml"/>
</mappers>
- 测试代码
public class UserDaoTest {
@Test
public void test() {
//获得session对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//方式一:getMapper执行sql
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
//关闭SqlSession
sqlSession.close();
}
}
2、CRUD
1、namespace
namespace中的报名要喝Da/mapper接口的包名一致
2、select
选择,查询语句:
id:对应namespace接口中的方法名
resultType:Sql语句执行的返回值
parameterType:参数类型
<select id="getUserList" resultType="com.darker.pojo.User"> select * from user </select>
3、insert
<insert id="addUser" parameterType="com.darker.pojo.User">
insert into user
values (#{id}, #{name}, #{password});
</insert>
4、update
<update id="updateUser" parameterType="com.darker.pojo.User">
update user
set name = #{name},
password = #{password}
where id = #{id};
</update>
5、delete
<delete id="deleteUser" parameterType="int">
delete
from user
where id =
#{id}
</delete>
6、注意点
增删改需要sqlSession.commit();
提交事物。
7、Map
假设实体类中的字段过多,考虑使用Map。
@Test
public void addUser2() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> hashMap = new HashMap<String, Object>();
hashMap.put("userid",5);
hashMap.put("username","safasdf");
hashMap.put("userpassword","2341212");
mapper.addUser2(hashMap);
sqlSession.close();
}
<insert id="addUser2" parameterType="map">
insert into user
values (#{userid}, #{username}, #{userpassword});
</insert>
//新增用户
int addUser2(HashMap<String, Object> map);
8、模糊查询
在传参是使用%%
List<User> userList = mapper.getUserLikeList("%测试%");
SQL中写
select * from user where name like "%"#{value}"%"
3、配置解析
1、核心配置文件
- mybatis-config.xml
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
1.1、环境配置(environments)
MyBatis 可以配置成适应多种环境,但每个 SqlSessionFactory 实例只能选择一种环境。
Mybatis默认的事物管理器是JDBC,连接池:POOLED;
1.2、属性(properties)
可以通过properties属性来实现引用配置文件【db.properties】
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="handhand"/>
</properties>
可以引入外部配置文件
可以在其中增加属性配置
如果有重复的字段,优先使用外部配置文件
1.3、类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写
- 给实体类起别名
<typeAliases>
<typeAlias type="com.darker.pojo.User" alias="User"/>
</typeAliases>
- 扫描实体类的包,他的默认别名就为这个类名的首字母小写
<typeAliases>
<package name="com.darker.pojo">
</typeAliases>
@Alias("helloUser")
1.4、设置(settings)
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 |
true | false | false |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true | false | False |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
1.5、映射器(mappers)
注册绑定我们的mapper文件
<!--每一个Mapper.xml都需要在Mybatis的核心配置文件中注册-->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
1.6、生命周期和作用域
SqlSessionFactoryBuilder
- 一旦创建了 SqlSessionFactory,就不再需要它了。
- 局部变量
SqlSessionFactory
- SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
- SqlSessionFactory 的最佳作用域是应用作用域
- 最简单的就是使用单例模式或者静态单例模式。
SqlSession
- SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
- 用完后关闭
4、解决属性名和字段名不一致的问题
实体
private int id; private String name; private String pwd;//与数据库不对应
数据库字段
id name password
解决方法
- 起别名
<select id="getUserById" resultType="com.darker.pojo.User" parameterType="int">
select id,name,password as pwd
from user
where id = #{id}
</select>
- resultMap
结果集映射
<resultMap id="UserMap" type="com.darker.pojo.User">
<!--column数据库中的字段,property实体类中的属性-->
<result column="id" property="id"/>
<result column="name" property="name"/>
<result column="password" property="pwd"/>
</resultMap>
<select id="getUserById" resultMap="UserMap" parameterType="int">
select *
from user
where id = #{id}
</select>
5、日志
5.1日志工厂
Mybatis提供的日志工厂
- SLF4J
- LOG4J
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING
- NO_LOGGING
STDOUT_LOGGING标准日志输出
Opening JDBC Connection
Created connection 1073763441.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@40005471]
==> Preparing: select * from user where id = ?
==> Parameters: 4(Integer)
<== Columns: id, name, password
<== Row: 4, q3142q测试, 23131
<== Total: 1
User{id=4, name='q3142q测试', pwd='23131'}
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@40005471]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@40005471]
Returned connection 1073763441 to pool.
5.2、Log4j
log4j.rootLogger=DEBUG,console,file
#控制台输出相关配置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold = DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern = [%c] -%m%n
#文件输出相关的配置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File = ./log/darker.log
log4j.appender.file.MaxFileSize = 10MB
log4j.appender.file.Threshold = DEBUG
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = [%p][%d{yyyy-MM-dd HH:mm:ss}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
简单使用
1.导包
import org.apache.log4j.Logger;
2.日志对象,参数为当前类的class
Logger logger = Logger.getLogger(UserDaoTest.class);
3.使用
logger.info("info:进入testLog4j");
logger.debug("debug:进入testLog4j");
logger.error("error:进入testLog4j");
6、分页
减少数据的处理量
使用Limit分页
select * from user limit startIndex,pageSize
使用RowBounds
SqlSession sqlSession = MybatisUtils.getSqlSession();
//offset,limit.从查询的第2行开始数,往后面四行数据
RowBounds rowBounds = new RowBounds(1, 4);
List<User> userLise = sqlSession.selectList("com.darker.dao.UserMapper.getUserByRowBounds", null, rowBounds);
for (User user : userLise) {
System.out.println(user.toString());
}
sqlSession.close();
7.注解开发
在接口上实现
@Select("select * from user")
List<User> getUserListAnn();
配置文件中绑定接口
<mapper class="com.darker.dao.UserMapper"/>
方法中如果有多个参数,一定要使用
@Param("id")
- 基本类型的参数或者String类型,需要加上
- 引用类型不需要加上
- 如果只有一个基本来兴,可以忽略
8、多对一的处理
需求:查询学生对应的老师信息
- 学生实体类
private long id;
private String name;
//关联老师
private Teacher teacher;
- 老师实体类
private long id;
private String name;
按照查询嵌套处理
<select id="getStdentAllInfo" resultMap="StudentTeacher">
select s.id, s.name, s.tid
from student s
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!-- 复杂的属性,需要单独处理
association:对象
collection:集合
-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select *
from teacher where id = #{id}
</select>
按照结果嵌套处理
<select id="getStdentAllInfo2" resultMap="StudentTeacher2">
select s.id sid, s.name sname, t.name tname
from student s
left join teacher t on s.tid = t.id;
</select>
<resultMap id="StudentTeacher2" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
9、一对多的处理
需求:查询老师下的学生信息
- 老师实体类
private long id;
private String name;
private List<Student> students;
- 学生实体类
private long id;
private String name;
private int tid;
按照查询嵌套处理
<select id="getTeacherAllInfo2" resultMap="TeacherSdudent2">
select t.id tid, t.name tname
from teacher t
</select>
<resultMap id="TeacherSdudent2" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" column="tid" javaType="ArrayList" ofType="Student" select="getStudent"/>
</resultMap>
<select id="getStudent" resultType="Student">
select * from student where tid = #{tid}
</select>
按照结果嵌套处理
<!--案结果嵌套查询-->
<select id="getTeacherAllInfo" resultMap="TeacherSdudent">
select t.id tid, t.name tname, s.id sid, s.name sname
from student s,
teacher t
where s.tid = t.id
</select>
<resultMap id="TeacherSdudent" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!-- javaType:指定属性的类型
集合中的泛型信息,用ofType获取-->
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
小结
1.关联 association【多对一】
2.集合 collection 【一对多】
3.javaType 和 ofType
javaType 用来指定实体类中的属性类型
ofType用来指定映射到List或者集合中的POJO类型,泛型中的约束类型
10、动态SQL
在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
if
if标签中tes属性必输
<select id="queryBlogIF" parameterType="map" resultType="Blog">
select * from blog where 1=1
<if test="title != null">
and title like #{title}
</if>
<if test="author != null">
and author like #{author}
</if>
</select>
choose (when, otherwise)
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
<select id="queryBlogChoose" parameterType="map" resultType="Blog">
select * from blog
<where>
<choose>
<when test="title != null">
and title like #{title}
</when>
<when test="author != null">
and author like #{author}
</when>
<otherwise>
1=1
</otherwise>
</choose>
</where>
</select>
trim (where, set)
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。
<update id="updateBlog" parameterType="map">
update blog
<set>
<if test="title !=null">
title = #{title},
</if>
<if test="author !=null">
author = #{author},
</if>
</set>
where id = #{id}
</update>
foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!
提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
<select id="queryBlogForeach" parameterType="map" resultType="Blog">
select * from blog
<where>
<foreach item="id" collection="ids" open="and (" separator="or" close=")">
id = #{id}
</foreach>
</where>
</select>
sql拼接结果:select * from blog WHERE ( id = ? or id = ? )
SQL片段
将公共的部分抽取出来,方便复用
使用sql标签抽取公共部分
在要用的时候是用include标签引用
<sql id="if-title-authoe">
<if test="title != null">
and title like #{title}
</if>
<if test="author != null">
and author like #{author}
</if>
</sql>
<select id="queryBlogIF" parameterType="map" resultType="Blog">
select * from blog where 1=1
<include refid="if-title-authoe"/>
</select>
11、缓存
Mybatis缓存
- mybatis默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启,(Sqlsession级别的缓存,也成为本地缓存)
- 二级缓存需要手动开启,他是namespace级别的环境
- Mybatis还定义了缓存接口Cache,可以通过实现Cache接口来定义二级缓存
一级缓存
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
- 手动清除缓存
sqlSession.clearCache();
二级缓存
要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行
<cache/>
这些属性可以通过 cache 元素的属性来修改。比如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
可用的清除策略有:
LRU
– 最近最少使用:移除最长时间不被使用的对象。FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认的清除策略是 LRU。
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,(也就是一级缓存SqlSession .close()之后)但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
原理
用户查询的时候,首先查看二级缓存中有没有,
如果没有则去查看一级缓存有没有,
没有就会去查询数据库。
自定义缓存ehcache
ehcache是一个分布式缓存
依赖:
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>