数据持久化
为什么要持久化?
Dao层、Service层、Controller层
思路:搭建环境 --> 导入MyBatis --> 编写代码 --> 测试
新建项目
创建一个普通的maven项目
删除src目录 (就可以把此工程当做父工程了,然后创建子工程)
导入maven依赖
<dependencies>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.6version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.42version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.11version>
<scope>testscope>
dependency>
dependencies>
<build>
<resources>
<resource>
<directory>src/main/resourcesdirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>truefiltering>
resource>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>truefiltering>
resource>
resources>
build>
创建一个Module
编写mybatis的核心配置文件
<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/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="com/jay/dao/UserMapper.xml"/>
mappers>
configuration>
编写mybatis工具类
package com.jay.util;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
/**
* @description:
* @author: jay
* @create: 2021-03-16 00:28
**/
public class MybatisUtils {
static SqlSessionFactory sqlSessionFactory = null;
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,顾名思义,我们可以从中获得 SqlSession 的实例.
// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
实体类
Dao接口
接口实现类 (由原来的UserDaoImpl转变为一个Mapper配置文件)
测试
@Test
public void getUserList() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
namespace中的包名要和Dao/Mapper接口的包名一致
选择,查询语句;
id:就是对应的namespace中的方法名;
resultType : Sql语句执行的返回值;
parameterType : 参数类型;
假设,我们的实体类,或者数据库中的表,字段或者参数过多,我们应该考虑使用Map!
通配符% %
mybatis-config.xml
configuration(配置)
MyBatis默认的事务管理器就是JDBC ,连接池:POOLED
db.properties
driver=com.mysql.jdbc.Driver
# &serverTimezone=UTC # 新版需要
url=jdbc:mysql://localhost:3306/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=root
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="xxxx"/>
properties>
外部资源属性>内部资源属性
指定类
指定包 取别名
<typeAliases>
<package name="com.jay.pojo"/>
typeAliases>
@Alias("xxx") // 需要配合
public class User {
}
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
MapperRegistry:注册绑定我们的Mapper文件;
<mappers>
<mapper resource="com/jay/mapper/UserMapper.xml"/>
mappers>
<mappers>
<mapper class="com.jay.mapper.UserMapper"/>
mappers>
<mappers>
<package name="com.jay.mapper"/>
mappers>
声明周期和作用域是至关重要的,因为错误的使用会导致非常严重的并发问题。
mybatis-config.xml --> SqlSessionFactoryBuilder --> SqlSessionFactory --> SqlSession --> mapper
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。
因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。
因此 SqlSessionFactory 的最佳作用域是应用作用域。
有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
每个线程都应该有它自己的 SqlSession 实例。
SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。
也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。
如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。
换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。
这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。
<select id="getUserById" resultType="com.kuang.pojo.User">
select id,name,pwd as password from USER where id = #{id}
select>
<resultMap id="UserMap" type="User">
<result column="id" property="id">result>
<result column="name" property="name">result>
<result column="pwd" property="password">result>
resultMap>
<select id="getUserList" resultMap="UserMap">
select * from USER
select>
resultMap 元素是 MyBatis 中最重要最强大的元素。
ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
ResultMap 的优秀之处——你完全可以不用显式地配置它们。
如果这个世界总是这么简单就好了。
如果一个数据库操作,出现了异常,我们需要排错,日志就是最好的助手!
曾经:sout、debug
现在:日志工厂
在MyBatis中具体使用哪一个日志实现,在设置中设定
STDOUT_LOGGING
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
settings>
Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件;
我们也可以控制每一条日志的输出格式;
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程;
最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
先导入log4j的包
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
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/rzp.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.sq1.PreparedStatement=DEBUG
配置settings为log4j实现
测试运行
Log4j简单使用
在要使用Log4j的类中,导入包 import org.apache.log4j.Logger;
日志对象,参数为当前类的class对象
Logger logger = Logger.getLogger(UserMapperTest.class);
日志级别
logger.info("info: 测试log4j");
logger.debug("debug: 测试log4j");
logger.error("error:测试log4j");
思考:为什么分页?
SELECT * from user limit startIndex,pageSize
使用MyBatis实现分页,核心SQL
接口
List<User> selectUsersByLimit(Map map);
Mapper.xml
<select id="selectUsersByLimit" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from user
limit #{startIndex},#{pageSize}
select>
测试
private SqlSessionFactory sqlSessionFactory;
@Before
public void before() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void selectUsersByLimit() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Map map = new HashMap<>();
map.put("startIndex",1);
map.put("pageSize",2);
List<User> users = userMapper.selectUsersByLimit(map);
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
不再使用SQL实现分页
接口
List<User> getUserByRowBounds();
mapper.xml
<select id="getUserByRowBounds" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from user
select>
测试
@Test
public void getUserByRowBounds() {
SqlSession sqlSession = sqlSessionFactory.openSession();
RowBounds rowBounds = new RowBounds(1, 2);
List<User> users = sqlSession.selectList("com.jay.mapper.UserMapper.getUserByRowBounds", null, rowBounds);
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
Mybatis PageHepler
引包
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.2.0version>
dependency>
接口
List<User> getUserByPageHelper();
xml
<select id="getUserByPageHelper" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from user
select>
测试
@Test
public void getUserByPageHelper() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Page page = PageHelper.startPage(2,1);
// Page page = PageHelper.offsetPage(1, 1);
List<User> users = userMapper.getUserByPageHelper();
logger.info("total ==> " + page.getTotal());
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
三个面向区别
注解在接口上实现
@Select("select * from user")
List<User> getUsers();
需要在核心配置文件中绑定接口
<mappers>
<mapper class="com.kuang.dao.UserMapper"/>
mappers>
本质:反射机制实现
底层:动态代理
//方法存在多个参数,所有的参数前面必须加上@Param("id")注解
@Delete("delete from user where id = ${uid}")
int deleteUser(@Param("uid") int id);
关于@Param( )注解
#{} 和 ${}
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.10version>
<scope>providedscope>
dependency>
@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String password;
}
实体类
@Data
public class Teacher implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
private Integer age;
List<Student> students;
}
@Data
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
private Integer age;
private Integer tId;
private Teacher teacher;
}
StudentDao
List<Student> selectAll();
List<Student> selectAllByTid(Integer tId);
StudentDao.xml
<resultMap id="StudentTeacher" extends="BaseResultMap" type="student">
<association property="teacher" fetchType="lazy" column="t_id" javaType="teacher"
select="com.jay.dao.TeacherDao.selectByPrimaryKey"/>
resultMap>
<select id="selectAll" resultMap="StudentTeacher">
select
<include refid="Base_Column_List"/>
from student
select>
<select id="selectAllByTid" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from student
where t_id = #{tId}
select>
TeacherDao
List<Teacher> selectAll();
TeacherDao.xml
<resultMap id="TeacherStudent" extends="BaseResultMap" type="teacher">
<collection property="students" fetchType="lazy" column="id" ofType="student" javaType="arrayList"
select="com.jay.dao.StudentDao.selectAllByTid">
collection>
resultMap>
<select id="selectAll" resultMap="TeacherStudent">
select
<include refid="Base_Column_List"/>
from teacher
select>
注意点:
创建一个基础工程
导包
编写配置文件
编写实体类
@Data
public class Blog implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String title;
private String author;
private Date createTime;
private Integer views;
}
编写实体类对应Mapper接口和Mapper.xml文件
注意事项:
元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。比如:
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
select>
动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合就可以了
建议:
查询 : 连接数据库,耗资源
一次查询的结果,给他暂存一个可以直接取到的地方 --> 内存:缓存
我们再次查询的相同数据的时候,直接走缓存,不走数据库了
测试步骤:
缓存失效的情况:
一级缓存开启(SqlSession级别的缓存,也称为本地缓存)
步骤:
开启全局缓存
<setting name="cacheEnabled" value="true"/>
在Mapper.xml中使用缓存
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
测试
小结:
注意:
<select id="getUserById" resultType="user" useCache="true">
select * from user where id = #{id}
select>
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存
导包
<dependency>
<groupId>org.mybatis.cachesgroupId>
<artifactId>mybatis-ehcacheartifactId>
<version>1.2.1version>
dependency>
在mapper中指定使用我们的ehcache缓存实现
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>