1.1 持久层
数据持久化
- 持久化就是将程序的数据,在持久状态和瞬时状态转化的过程
2. Mybatis的创建
简介:
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory
的实例为核心的。SqlSessionFactory
的实例可以通过 SqlSessionFactoryBuilder
获得。而 SqlSessionFactoryBuilder
则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
-
需要先配置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"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!-- 每个Mapper.xml都需要在Mybatis核心配置文件中注册--> <mappers> <mapper resource="mapper/UserMapper.xml"/> </mappers> </configuration>
-
使用
SqlSessionFactoryBuilder
创建SqlSessionFacto
对象,通过SqlSessionFacto
象创建一个SqlSession
的实例//使用Mybatis第一步:获取sqlSessionFactory对象 String resource = "mybatis-config.xml"; //读取核心配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); //字节流配置信息 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //创建SqlSessionFacto对象 //有了sqlsessionfactory,就可以获得SQL session的实例 //sqlsession包含面向数据库执行SQL命令的所有方法 public static SqlSession getSqlSession() { return sqlSessionFactory.openSession(); }
3.工具类编写
private static SqlSessionFactory sqlSessionFactory; static { try { //使用Mybatis第一步:获取sqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); }catch(IOException e) { e.printStackTrace(); } } //有了sqlsessionfactory,就可以获得SQL session的实例 //sqlsession包含面向数据库执行SQL命令的所有方法 public static SqlSession getSqlSession() { return sqlSessionFactory.openSession(); }
2.2 代码的编写
-
实体类(POJO)
-
Dao接口(DAO层-数据访问层)
-
接口实现类(Service层)–由原来的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"> <!--namespace 绑定一个对应的DAO/Mapper接口_(具体写该类的相对路径)--> <mapper namespace="com.znx.dao.UserDao"> <!--查询语句 id对应Dao接口的方法的名字,返回类型也写具体写该类的相对路径--> <select id="getUserList" resultType="com.znx.POJO.User"> select * from mybatis.user </select> </mapper>
2.3 测试
注意的点:
org.apache.ibatis.binding.BindingException: Type interface com.kuang.dao.UserDao is not known to theMapperRegistry.
出现这个,是因为Mapper.xml文件问题,需要把该文件映射到mybatis核心文件中,此外,把Mapper文件放到resources文件下, 这样比较好管理。
测试代码块
public void test()
{
//获取Sqlsession
SqlSession sqlSession = MybatisUtils.getSqlSession();
//通过反射获得sql--方式一getMapper
//通过接口的Class对象--因为UserDao和UserMapper.xml都是同一个东西,
//UserMapper.xml实现了UserDao的接口。
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> userList = mapper.getUserList();
for (User user : userList)
{
System.out.println(user);
}
//关闭SqlSession
sqlSession.close();
}
3 增删改查
-
namespace–命名空间
在Mapper.xml文件中,
namespace
要与对应的接口类对于(就是该接口的相对路径) -
select
选择、查询语句
- id:与
namespace
中的方法名相对于(大小写一直) - resultType:查询结果返回值的类型
- parameterType:传入的参数类型
- id:与
-
增删改–insert、update
增删改必须进行事务提交,JDBC默认是关闭事务的,若不提交,则不会进行数据的增删改
-
具体步骤
-
编写接口
//插入数据 int insertUser(User user);
-
在mapper中编写对应的sql语句
<!--插入数据--> <insert id="insertUser" parameterType="com.znx.POJO.User"> insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd}); </insert>
-
测试代码是否可行
@Test public void insertuser() { //获取sqlsession SqlSession sqlSession = MybatisUtils.getSqlSession(); //去获取映射文件 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); int a = userMapper.insertUser(new User(4, "QQ", "123456")); //提交事务 sqlSession.commit(); if (a > 0) System.out.println("插入成功"); sqlSession.close(); }
-
5. 万能的Map使用
如果实体类或者数据库的表,字段或者参数过多,应当考虑使用Map。
//按id进行查询-Map
User getUserById2(Map<String, Object> map);
<!--按id进行查询,map-->
<select id="getUserById2" resultType="com.znx.POJO.User" parameterType="map">
select * from mybatis.user where id = #{userid} and name = #{username};
</select>
@Test
public void getuserbyid2()
{
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<String, Object>();
map.put("userid", 4);
map.put("username", "WWW");
User user = userMapper.getUserById2(map);
System.out.println(user.toString());
sqlSession.close();
}
-
Map传递参数,直接在sql中取出key即可。–
parameterType="map"
-
对象传递参数,直接在sql中取出对象的属性即可。
parameType = '对象类名'
-
只有一个基本类型参数的情况下,可以直接在sql中取到
-
多个参数可以用Map,或者注解
6. 模糊查询
-
java代码执行时,传递通配符
%
List<User> userlist = mapper.getUserLike("%A%");
-
在sql拼接中使用通配符
select * from mybatis.user where name like "%"#{name}"%";
4. MXL文件属性
1. 属性(properties)
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。
如果在properties中设置同一个元素属性,那么mybatis会按照下面的顺序来加载:
- 首先读取在 properties 元素体内指定的属性。
- 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
- 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
2.设置别名
类型别名–typeAliases
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
<!--设置别名-->
<typeAliases>
<typeAlias type="com.znx1.pojo.User" alias="user"/>
</typeAliases>
也可以指定一个包名,mybatis会在包下搜索需要的java Bean。
如:扫描实体类的包,它的默认名是这个类的类名。且首字母小写。
<typeAliases>
<!--指定包并扫描-->
<package name="com.znx1.pojo"/>
</typeAliases>
在实体类较少的情况下,使用类指定的方式。
如果实体类十分多,建议使用第二种方式
第一种可以DIY别名,第二种则不行,如过需要改,则必须在实体类上增加注解
@Alias("hello")
public class User
3. 映射器
MapperRegistry:注册绑定我们的Mapper文件
方式一:
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
PS:文件位置不受接口位置和名字影响,只需要写对路径
方式二:用class文件映射
<mappers>
<mapper class="com.znx1.dao.UserMapper"/>
</mappers>
方式二的注意点:
- 接口和他的Mapper配置文件必须同名
- 接口和他的Mapper配置文件必须在同一个包下
方式三:使用指定包进行绑定
<mappers>
<mapper class="com.znx1.dao"/>
</mappers>
注意点:
- 接口与Mapper配置文件需要同名
- 接口与Mapper配置文件需在同一个包下
6.生命周期和作用域
SqlSessionFactoryBuilder
-
一旦创建了SqlSessionFactory
-
局部变量
SqlSessionFactory
- 可以想象为:数据库连接池
SqlSessionFactory
一旦被创建,就应该在应用的运行期间一直存在,没有任何理由丢弃或重新创建了一个实例- 所以
SqlSessionFactory
应该放在全局变量
SqlSession
- 链接到连接池的一个请求
- SqlSession的实例不是线程安全的,因此不能被共享的,所以它的最佳作用域是请求或方法作用域。
- 用完之后需要立即关闭,否则资源被占用。
7.解决属性名和字段名不一致
- 解决方法–起别名
<!--按id进行查询-->
<select id="getUserById" resultType="com.znx1.pojo.User" parameterType="int">
select id, name, pwd as password from mybatis.user where id = #{id};
</select>
- 结果集映射
<!--按id进行查询-->
<resultMap id="UserMap" type="User">
<!--column数据库的中字段, property类中的属性 -->
<id column="id" property="id"/>
<id column="name" property="name"/>
<id column="pwd" property="password"/>
</resultMap>
<select id="getUserById" resultMap="UserMap" parameterType="int">
select * from mybatis.user where id = #{id};
</select>
7.Log4j
- Log4j,可以控制日志信息输送到目的是控制条、文件等
- 可以控制每一条日志的输出格式
- 通过定义每一条日志信息的级别,可以更细致的控制日志的生产过程
- 通过配置文件灵活配置,而不需要修改应用代码
- 导入log4j包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
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/kuang.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{yy-MM-dd}][%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
-
log4j配置
<settings> <setting name="logImpl" value="LOG4J"/> </settings>
7.2 简单的使用
-
在要使用Log4j的类中,导入包
org.apache.log4j.Logger
-
创建日志对象,参数为当前类的clss
private static Logger logger = Logger.getLogger(Testdao.class);
8. 分页查询
8.1
- 接口方法
//分页查询
List<User> getUserByLimit(Map<String,Integer> map);
-
查询语句
<!--分页查询--> <select id="getUserByLimit" parameterType="map" resultMap="UserMap"> select * from mybatis.user limit #{startIndex}, #{pageSize}; </select>
-
测试实现
@Test public void testLimit() { SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map<String, Integer> map = new HashMap<String, Integer>(); map.put("startIndex", 0); map.put("pageSize", 2); List<User> users = mapper.getUserByLimit(map); for (User user : users) { System.out.println(user); } sqlSession.close(); }
8.2RowBounds实现
不再使用SQL实现分页
-
接口
//RowBounds查询分页 List<User> getUserByRowBounds();
-
查询语句
<select id="getUserByRowBounds" resultMap="UserMap"> select * from USER ; </select>
-
测试
@Test public void getUserByRowBounds() { SqlSession sqlSession = MybatisUtils.getSqlSession(); RowBounds rowBounds = new RowBounds(0, 2); //通过java层面实现分类 List<User> users = sqlSession.selectList("com.znx1.dao.UserMapper.getUserByRowBounds", null, rowBounds); for (User user : users) { System.out.println(user); } sqlSession.close(); }
9. Mybatis的执行流程
-
通过Resource加载配置好的
mybatis-config.xml
文件 通过
getResourceAsStream
把文件加载进来 -
创建一个
SqlSessionFactoryBuilder
对象,构建一个SqlSessionFactory
2.1 Buider调用
build()
方法,改方法里利用XMLconfigBuilder
对象对上面加载的配置文件进行解析,然后返回一个configuration
对象。 2.2 最后,
configuration
对象作为参数,构建SqlSessionFactory
对象,并最终返回该对象 -
Transaction事务管理器
在实例化
SqlSessionFactory
对象后,有一个Transaction
事务,由它进行创建executor
执行器,最终才得到SqlSession
对象
10.CRUD
可以在工具类创建的时候,自动提交事务
public static SqlSession getSqlSession()
{
return sqlSessionFactory.openSession(true);
}
通过注解的方式,编写sql语句
public interface UserMapper
{
//获取全部人员
@Select("select * from mybatis.user")
List<User> getUserList();
//使用注解方式写sql
//按id查询
@Select("select * from mybatis.user where id = #{id}")
User getUserById(@Param("id") int id);
//多个参数查询
@Select("select * from mybatis.user where id = #{id} and name = #{name}")
User getUserByIdAndName(@Param("id") int id, @Param("name") String name);
//增加数据
@Insert("insert into mybatis.user(id, name, pwd) values(#{id}, #{name}, #{password})")
int addUser(User user);
//修改数据
@Update("update mybatis.user set name = #{name}, pwd = #{password} where id = #{id}")
int updateUser(User user);
//删除数据
@Delete("delete from mybatis.user where id = #{uid}")
int deleteUser(@Param("uid") int id);
}
**PS:**如果使用该方法,需要在mybatis-config.xml
文件中,绑定该Mapper接口.
关于@Param()注解
- 在SQL中引用的就是这里的
@Param("uid")
中设定的属性名 - 引用类型不需要加
11.多表查询
一、多对一查询
<!--多对一处理-->
<!--思路
1.查找学生的信息
2.根据学生对应的tid,寻找对应的老师
-->
<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="getStudent" resultMap="StudentTeacher">
select * from student;
</select>
<select id="getTeacher" resultType="Teacher">
select * from teacher where id = #{id};
</select>
2. 按照结果嵌套处理
<!--方式二-->
<!--按照结果嵌套查询-->
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id as sid , s.name as sname, t.name as tname from student s, teacher t where 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>
二、一对多查询
-
按照结果嵌套查询
<!--方式一,嵌套结果集--> <select id="getTeacherById" resultMap="TeacherStudent"> select s.id as sid, s.name as sname, t.id as tid, t.name as tname from student s, teacher t where s.tid = t.id and t.id = #{tid}; </select> <!--复杂的属性,需要单独处理 对象:association 集合:collection javaType="" -指定的属性类别 在集合中的泛型数据,我们使用ofType获取 --> <resultMap id="TeacherStudent" type="Teacher"> <result property="id" column="tid"/> <result property="name" column="tname"/> <collection property="students" ofType="Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <result property="tid" column="tid"/> </collection> </resultMap>
-
按照查询嵌套处理
<!--方式二 嵌套子查询-->
<!--查询出老师的信息-->
<select id="getTeacherById2" resultMap="TeacherStudent2">
select * from mybatis.teacher where id = #{id};
</select>
<resultMap id="TeacherStudent2" type="Teacher">
<collection property="students" column="id" javaType="ArrayList" ofType="Student" select="getStudentByTeacher"/>
</resultMap>
<!--查询出老师对应的所以学生-->
<select id="getStudentByTeacher" resultType="Student">
select * from mybatis.student where tid = #{tid};
</select>
小结:
- 关联:- association —【多对一】
- 集合:- collection —【一对多】
- JavaType 与 ofType的区别
- JavaType 用来指定实体类中属性的类型
- ofType 用来指定映射到List 或者集合中的 pojo 类型,也就是说,泛型中的约束类型!
11.动态SQL
动态SQL:指根据不同的条件生成不同的SQL语句
<select id="getBlogByIf" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
-
choose(when、otherwise)
<!--choose使用方法类似switch--> <select id="getBlogByChoose" parameterType="map" resultType="Blog"> select * from mybatis.blog <where> <choose> <when test="title != null"> title = #{title} </when> <when test="author != null"> and author = #{author} </when> <otherwise> and views = #{views} </otherwise> </choose> </where> </select>
动态SQL本质上还是SQL语句,只是我们可以在SQL层面去执行一个逻辑代码
11.1 SQL片段
有时候,我们可能会将一些功能的部分抽取出来,方便复用
-
使用SQL标签抽取公共部分
<!--where 使用SQL片段--> <sql id="if-title-author"> <if test="title != null"> and title = #{title} </if> <if test="author != null"> and author = #{author} </if> </sql>
-
在需要使用的地方使用
include
标签引入即可
<select id="getBlogByWhere2" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
<include refid="if-title-author"></include>
</where>
</select>
PS:
-
最好使用单表来定义SQL片段–目的是避免复杂查询
-
不要存在where标签–这样的话,就不是sql语句复用了
Foreach标签
<select id="getBlogByForeach" resultType="Blog" parameterType="map">
select * from mybatis.blog
<where>
<foreach collection="idList" item="id" open="(" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
collection
是外部传进来的集合,item
是遍历集合, open
是以什么开头进行拼接, close
是以什么结尾进行拼接,separator
是相当于分隔符
@Test
public void test5()
{
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
ArrayList arrayList = new ArrayList();
arrayList.add(1);
map.put("idList", arrayList);
List<Blog> blogByForeach = mapper.getBlogByForeach(map);
for (Blog byForeach : blogByForeach) {
System.out.println(byForeach);
}
sqlSession.close();
}
动态SQL就是在拼接SQL语句,我们只要保持SQL的正确性,按照SQL的格式去排列组合就可以了
PS:
- 可以先在Mysql中写出完整的SQL,再对应的去修改成为我们的动态SQL实现通用即可。
12 缓存
-
使用缓存:减少和数据库的交互次数,减少系统开销,提高系统效率
-
经常查询且不经常改变的数据能使用缓存
-
一级缓存
- 一级缓存也叫本地缓存:SQL Session
- 与数据库同一次会话期间,查询到的数据会放在本地缓存中
- 以后如果需要获取相同的数据,直接从缓存中拿,没必要再次查询数据库
-
缓存失效的情况
-
查询不同的东西
-
增删改操作,有可能改变原来的数据,所以必定会刷新缓存
-
-
查询不同的Mapper.xml
-
手动清理缓存
//清理缓存 sqlSession.clearCache();
12.2二级缓存
- 二级2缓存也叫全局缓存,一级缓存作用域低,所以有二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
- 工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
- 如果当前会话关闭了,这个会话对应的一级缓存也没了。但我们要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
- 新的会话查询信息,就可以从二级缓存中获取内容
- 不同的mapper查出的数据,会放在自己对应的缓存中
步骤:
-
开启全局缓存
<setting name="cacheEnabled" value="true"/>
-
在要使用二级缓存的Mapper中开启
<!--在Mapper中开启二级缓存-->
<cache/>
也可以自定义缓存配置
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
-
可能出现的问题
1.如果没有自定义配置参数,会出现报错
Error serializing object. Cause: java.io.NotSerializableException: com.znx1.pojo.User
解决:把实体类序列化即可,或者添加上面的配置参数的
readOnly="true"
!!!
小结:
- 只要开启了二级缓存,在同一个Mapper下就有效
- 所有数据都会先放在一级缓存中
- 只有当会话提交、或关闭的时候,才会提交到二级缓存中
//清理缓存
sqlSession.clearCache();
```
#### 12.2二级缓存
- 二级2缓存也叫全局缓存,一级缓存作用域低,所以有二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
- 工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
- 如果当前会话关闭了,这个会话对应的一级缓存也没了。但我们要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
- 新的会话查询信息,就可以从二级缓存中获取内容
- 不同的mapper查出的数据,会放在自己对应的缓存中
步骤:
1. 开启全局缓存
```xml
<setting name="cacheEnabled" value="true"/>
- 在要使用二级缓存的Mapper中开启
<!--在Mapper中开启二级缓存-->
<cache/>
也可以自定义缓存配置
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
-
可能出现的问题
1.如果没有自定义配置参数,会出现报错
Error serializing object. Cause: java.io.NotSerializableException: com.znx1.pojo.User
解决:把实体类序列化即可,或者添加上面的配置参数的
readOnly="true"
!!!
小结:
- 只要开启了二级缓存,在同一个Mapper下就有效
- 所有数据都会先放在一级缓存中
- 只有当会话提交、或关闭的时候,才会提交到二级缓存中