MyBatis是一款优秀的持久层框架
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
持久化:是将程序数据在持久状态和顺势状态间转换的机制
即把数据(如内存中的对象)保存到可永久保护存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存在数据库中,或者存储在磁盘文件中,XML数据文件中等等
JDBC就是一种持久化机制。文件IO也是一种持久化机制。
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
<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://localhost:3306/test?serverTimezone=GMT%2B8&characterEncoding=utf-8"/>
<property name="username" value="ltg"/>
<property name="password" value="991115"/>
dataSource>
environment>
environments>
configuration>
package com.ltg.utils;
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;
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static{
//使用Mybatis第一步,获取SqlSessionFactory 对象
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
package com.ltg.popl;
public class User {
private Integer id;
private String name;
private String pwd;
public User(){}
public User(Integer id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}
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 String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}
package com.ltg.dao;
import com.ltg.popl.User;
import java.util.List;
import java.util.Map;
public interface UserMapper {
//查询用户
List<User> getUserList();
//根据id查用户
User getUserById(int id);
//插入一个用户
void insertUser(User user);
//修改一个用户的信息
void updataUser(User user);
//删除一个用户
void deleteUser(int id);
/**/
//模糊查询
List<User> getLikeUser(String name);
//使用map进行传递参数实例
List<User> getUserById2(Map map);
}
<?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="com.ltg.dao.UserMapper">
<select id="getUserList" resultType="com.ltg.popl.User">
select * from mybatis.user
</select>
<select id="getUserById" resultType="com.ltg.popl.User">
select * from mybatis.user where id = #{id}
</select>
<insert id="insertUser" parameterType="com.ltg.popl.User">
insert into mybatis.user(id, name, pwd) values (#{id},#{name},#{pwd})
</insert>
<update id="updataUser" parameterType="com.ltg.popl.User">
update mybatis.user set name = #{name} , pwd = #{pwd} where id = #{id} ;
</update>
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id = #{id}
</delete>
<select id="getLikeUser" parameterType="String" resultType="com.ltg.popl.User">
select * from mybatis.user where name like "%"#{name}"%"
</select>
<select id="getUserById2" parameterType="map" resultType="com.ltg.popl.User">
select * from mybatis.user where id = #{helloid}
</select>
</mapper>
package com.ltg.dao;
import com.ltg.popl.User;
import com.ltg.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
public class UserMapperTest {
@Test
public void test(){
//第一步:获取sqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
List<User> userList = null;
try {
/*方式一*/
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userList = userMapper.getUserList();
/*方式二*/
// List userList = sqlSession.selectList("com.ltg.dao.UserDao.getUserList");
sqlSession.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
for (User user : userList) {
System.out.println(user);
}
}
}
@Test
public void addUserById() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User userById = mapper.getUserById(1);
System.out.println(userById);
sqlSession.close();
}
@Test
public void insertUser() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.insertUser(new User(4,"小李","123456"));
//!!!注意:增删改操作要提交事务
sqlSession.commit();
sqlSession.close();
}
@Test
public void updataUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.updataUser(new User(2 , "小花" , "99999"));
sqlSession.commit();
sqlSession.close();
}
@Test
public void deleteUser() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.deleteUser(2);
sqlSession.commit();
sqlSession.close();
}
@Test
public void getLikeUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> user = mapper.getLikeUser("小");
for (User user1 : user) {
System.out.println(user1);
}
sqlSession.close();
}
@Test
public void getUserById2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("helloid",1);
List<User> userById2 = mapper.getUserById2(map);
for (User user : userById2) {
System.out.println(user);
}
sqlSession.close();
}
}
1、在接口方法中,参数直接传递Map;
User selectUserByNP2(Map map);
2、编写sql语句的时候,需要传递参数类型,参数类型为map
3、在使用方法的时候,Map的 key 为 sql中取的值即可,没有顺序要求!
Map map = new HashMap();
map.put("username","小明");
map.put("pwd","123456");
User user = mapper.selectUserByNP2(map);
总结:如果参数过多,我们可以考虑直接使用Map实现,如果参数比较少,直接传递参数即可
<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>
<mappers>
<mapper resource="com/ltg/dao/UserMapper.xml"/>
</mappers>
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
environments>
配置MyBatis的运行多套环境,将SQL映射到不同的数据库上,必须指定其中一个默认运行环境(通过default指定)
子元素节点:environment
dataSource元素使用标准的JDBC数据源接口来配置JDBC连接对象的资源
数据源是必须配置的
有三种内建的数据源类型
type="[UNPOOLED|POOLED|JNDI]")
unpooled:这个数据源的实现只是每次被请求时打开和关闭连接。
pooled:这种数据源的实现利用“池”的概念将JDBC连接对象组织起来,这是一种使得并发Web应用快速响应请求的流行处理方式。
jndi:这个数据源的实现是为了能在如Spring或应用服务器这类容器中使用,容器可以集中在外部配置数据源,然后防止一个JNDI上下文应用/
数据源也有很多第三方的实现:dbcp,druid,c3p0…
子元素节点:transactionManager-[事务管理器]
这两张事务管理器类型都不需要设置任何属性
file:///
的 URL),或类名和包名等。映射器是MyBatis中最核心的组件之一,在MyBatis 3之前,只支持xml映射器,即:所有的SQL语句都必须在xml文件中配置。而从MyBatis 3开始,还支持接口映射器,这种映射器方式允许以Java代码的方式注解定义SQL语句,非常简洁。引入资源方式
Mapper的XML文件
MyBatis 的真正强大在于它的映射语句,和JDBC代码相比较,会减少大量的代码冗余,MyBatis为聚焦于SQL而构建,以尽可能地为你减少麻烦。
数据库这些属性都是可以外部配置且可动态替换的,既可以在典型的Java属性文件中配置,亦可以通过properties元素的子元素来传递。
我们可以优化一下我们的配置文件
第一步:正在资源目录下新建一个db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8
username=ltg
password=991115
第二步:将文件导入properties配置文件
如果外部配置文件和内部properties 有同一字段,优先使用外部配置文件
类型别名时为Java设置一个短的名字。它只和XML配置有关,存在的意义仅在于用来减少类完全限定名的冗余
1.当这样配置时,User
可以用在任何使用com.kuang.pojo.User
的地方。
2.也可以指定一个包名,MyBatis会在包名下搜索需要的JavaBean
每一个在包 com.kuang.pojo
中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。
3.也可使用注解
@Alias("user")
public class User {
...
}
设置
设置(settings)相关=>查看帮助文档
一个配置完整的settings元素的示例如下
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
settings>
类型处理器
对象工厂
MyBatis每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。
默认的对象工厂需要做的仅仅时实例化目标类,要么通过构造方法,要么在参数映射存在的时候通过有参构造方法来实例化。
如果像覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现。【了解即可】
作用域(scope)和生命周期
理解我们目前以及讨论过的不同作用于和生命周期类是至关重要的,因为错误的使用会导致非常严重的并发问题。
作用域理解
要解决的问题:属性名和字段名不一致
分析:
select*from user where id = #{id} 可以看做
select id , name , pwd , form user where id = #{id}
mybatis会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写),去对应的实体类中查找相应列名的set方法设值,由于找不到setPwd(),所以password返回null;
【自动映射】
解决方案
方案一:为列名指定别名,别名和Java实体类的属性名一致。
<select id="selectUserById" resultType="User">
select id , name , pwd as password from user where id = #{id}
</select>
方案二:使用结果集映射->ResultMap【推荐】
ResultMap
自动映射
你已经见过简单映射语句的示例了,但并没有显式指定 resultMap
。比如:
上述语句只是简单地将所有的列映射到 HashMap
的键上,这由 resultType
属性指定。虽然在大部分情况下都够用,但是 HashMap 不是一个很好的模型。你的程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为模型。
ResultMap
最优秀的地方在于,虽然你已经对它相当了解了,但是根本就不需要显式地用到他们。
手动映射
返回值类型为resultMap
编写resultMap,实现手动映射!
这些是比较简单的案列,数据库中还有一对多,多对一,后面还有比较高级的结果集映射,association,collection这些。
思考:我们在测试SQL时,如果能在控制台输出SQL的画,是不是就可以有更快的排错效率?
如果一个数据库相关的操作出了问题,我们可以根据输出的SQL语句来快速排查问题。
对于以往的开发过程,我们经常会用到debug模式来调节,跟踪我们的代码执行过程。但是现在使用MyBatis是基于接口,配置文件的源代码执行过程。因此,我们必须选择日志工具来作为我们开发,调节程序的工具。
MyBatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:
具体选择哪个日志实现工具有MyBatis的内置日志工厂确定,它会使用最先找到的(按上面顺序) 。 如果一个都没找到,日志功能就会被禁用。
标准日志实现
指定MyBatis应该使用哪个日志记录实现。如果此设置不存在,则会自动发现日志记录实现。
这是在控制台输出日志。
Log4j
简介:
使用步骤:
log4j
log4j
1.2.17
配置文件的编写
#将等级为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
setting设置日志实现
4.在程序中使用Log4j进行输出!
static Logger logger = Logger.getLogger(UserMapperTest.class);
@Test
public void testLog4j(){
logger.info("info:进入了testLog4j");
logger.debug("debug:进入了testLog4j");
logger.error("error:进入了testLog4j");
}
5.控制台有输出
还有一个日志生成的文件
log4j.appender.file.File=./log/ltg.log
思考:为什么需要分页?
在学习mybatis等持久层框架时,会经常对数据进行增删改查操作,使用最多的时对数据库进行查询操作,如果查询大量数据时,我们往往使用分页进行查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。
使用Limit实现分页
#语法
SELECT * FROM table LIMIT stratIndex,pageSize
SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15
#为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:
SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last.
#如果只给定一个参数,它表示返回最大的记录行数目:
SELECT * FROM table LIMIT 5; //检索前 5 个记录行
#换句话说,LIMIT n 等价于 LIMIT 0,n。
步骤
1、Mapper接口,参数为map
//使用limit进行分页查询
List getUserByLimit(Map map);
2、修改Mapper文件(limit需要的两个参数 startIndex 和 pageSize)
3、在测试类传入参数测试
@Test
public void getUserByLimit(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap map = new HashMap();
map.put("startIndex" , 1);
map.put("pageSize" , 3);
List userByLimit = mapper.getUserByLimit(map);
for (User user : userByLimit) {
System.out.println(user);
}
sqlSession.close();
}
我们除了使用Limit在SQL层面实现分页,也可以使用RowBounds在Java代码层面实现分页,当然此种方式作为了解即可。
步骤:
1、mapper接口
//使用RowBounds进行分页拆线呢
List getUserByRowBounds();
2、mappe文件
3、测试类测试(需要new一个RowBounds类出来)
@Test
public void getUserByRowBounds(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
RowBounds rowBounds = new RowBounds(2, 3);
//通过session**方法进行传递rowBounds,[!!这种方法现在以及不推荐使用了]
List users = sqlSession.selectList("com.ltg.dao.UserMapper.getUserByRowBounds", null, rowBounds);
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
了解即可,可以自己尝试使用
官方文档:https://pagehelper.github.io/
接口从更深层次理解,应是定义(规范,约束)与实现(名实分离的原则)的分离
接口本身反应了系统设计人员对系统抽象理解
接口应该有两类:
第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class)。
第二类是对一个个体某一方面的抽象,即形成一个抽线面(interface)。
一个个体可能有多个抽象面。抽象体与抽象面是有区别的。
!!注意:利用注解开发就不需要mapper.xml映射文件了
1、我们在我们的接口中添加注解
public interface UserMapper {
//查询所有用户
@Select("select * from user")
List getUser();
}
2、在mybatis的核心配置文件中注入
3、测试
@Test
public void getUserMapper(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List user = mapper.getUser();
for (User user1 : user) {
System.out.println(user1);
}
sqlSession.close();
}
5、本质上利用了jvm的动态代理机制
改造MybatisUtils工具类的getSession() 方法,重载实现。
//设置事务自动提交 flag为true时为自动提交事务
public static SqlSession getSqlSession(boolean flag){
return sqlSessionFactory.openSession(flag);
}
注意:确保实体类和数据库字段对应
查询:
1、编写接口方法注解
//按照id查询用户
@Select("select * from user where id = #{id}")
List getUserById(@Param("id") int id);
2、测试
增加
1、编写接口方法注解
//添加一个用户
@Insert("insert into user(id , name , pwd) values(#{id} , #{name} , #{password})")
int insertUser(User user);
2、测试
修改一条信息
1、编写接口方法注解
//修改一个用户信息
@Update("update user set name = #{name},pwd = #{password} where id = #{id}")
int updateUser(User user);
2、测试
删除一条信息
1、编写接口方法注解
//删除一条用户信息
@Delete("delete from user where id = #{id}")
void deleteUser(@Param("id") int id);
2、测试
关于@Param
@Param注解用于给方法参数起一个名字。以下时总结的使用原则
#和$的区别
#{}的作用主要是替换预编译语句(PrepareStatement)中的占位符 ?【推荐】
INSERT INTO user (name) VALUES (#{name});
INSERT INTO user (name) VALUES (?);
${}的作用是直接进行字符串的替换(sql注入问题)
INSERT INTO user (name) VALUES ('${name}');
INSERT INTO user (name) VALUES ('kuangshen');
使用注解和配置文件协同开发,才是Mybatis的最佳实战
多对一的理解:
数据库设计
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO teacher(`id`, `name`) VALUES (1, '李老师');
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
搭建测试环境
1、IDEA安装Lombok插件
2、引入Maven依赖
org.projectlombok
lombok
1.16.10
注意!!我没用上面这个插件
3、编写实体类Teacher和Student
4、编写实体类对应的Mapper接口【两个】
public interface StudentMapper {
}
public interface TeacherMapper {
}
5、编写Mapper接口对应的mapper.xml配置文件【两个】
1、给StudentMapper接口增加方法
public interface StudentMapper {
List getStudent();
}
2、编写对应的Mapper文件
第一种:按照查询嵌套处理
3、去mybatis-config核心配置文件注册一个Mapper!!
4、注意点说明
5、测试
1、就用上面那个接口
2、编写对应的mapper文件
3、测试
public void test2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List student = mapper.getStudent();
for (Student student1 : student) {
System.out.println(student1);
}
一对多的理解:
1、实体类和上面不同要修改
public class Student {
private int id;
private String name;
private int tid;
public class Teacher {
private int id;
private String name;
private List student;
1、TeacherMapper接口方法编写
public interface TeacherMapper {
//获取指定老师,以及老师下的学生
List getTeacher(int id);
}
2、编写接口对应的Mapper配置文件
3、注意注册Mapper文件到mybatis-config核心配置文件中
4、测试
@Test
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
List teacher = mapper.getTeacher(1);
for (Teacher teacher1 : teacher) {
System.out.println(teacher1);
}
sqlSession.close();
}
1、直接用上一个接口方法
2、编写接口对应的Mapper配置文件
第二种:按照查询嵌套处理
3、还是注意把Mapper注册到mybatis-config核心配置文件中
4、测试
小结
1、关联-association(多对一)
2、集合-collection(一对多)
4、JavaType和ofType都是用来指定对象类型的
注意点
面试必考
介绍
什么是动态SQL:动态SQL指的是根据不同的查询条件,生成不同的SQL语句
官网描述:
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射语句中的强大的动态 SQL 语言得以改进这种情形。
动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多元素需要花时间了解。MyBatis 3 大大精简了元素种类,现在只需学习原来一半的元素便可。MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。
-------------------------------
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
-------------------------------
我们之前写的 SQL 语句都比较简单,如果有比较复杂的业务,我们需要写复杂的 SQL 语句,往往需要拼接,而拼接 SQL ,稍微不注意,由于引号,空格等缺失可能都会导致错误。
那么怎么去解决这个问题呢?这就要使用 mybatis 动态SQL,通过 if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高 SQL 语句的准确性的同时,也大大提高了开发人员的效率。
搭建环境
新建一个数据库表:blog
字段:id,title,author,create_time,views
CREATE TABLE `blog` (
`id` varchar(50) NOT NULL COMMENT '博客id',
`title` varchar(100) NOT NULL COMMENT '博客标题',
`author` varchar(30) NOT NULL COMMENT '博客作者',
`create_time` datetime NOT NULL COMMENT '创建时间',
`views` int(30) NOT NULL COMMENT '浏览量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1、创建Mybatis基础工程
这里就不截图了
2、IdUtil工具类(目的是为了产生随机的id)
public class IdUtil {
public static String genId(){
return UUID.randomUUID().toString().replaceAll("-","");
}
}
3、实体类编写
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
4、编写Mpper接口以及对应的xml文件
public interface BlogMapper {
}
5、mybatis核心配置文件,设置下划线驼峰自动转换
6、插入原始数据
//新增一个博客
int addBlog(Blog blog);
insert into blog (id, title, author, create_time, views)
values (#{id},#{title},#{author},#{createTime},#{views});
@Test
public void addInitBlog(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(IDUtil.genId());
blog.setTitle("Mybatis如此简单");
blog.setAuthor("狂神说");
blog.setCreateTime(new Date());
blog.setViews(9999);
mapper.addBlog(blog);
blog.setId(IDUtil.genId());
blog.setTitle("Java如此简单");
mapper.addBlog(blog);
blog.setId(IDUtil.genId());
blog.setTitle("Spring如此简单");
mapper.addBlog(blog);
blog.setId(IDUtil.genId());
blog.setTitle("微服务如此简单");
mapper.addBlog(blog);
blog.setId(IdUtil.genId());
blog.setTitle("HTML如此简单");
blog.setAuthor("李刚");
blog.setCreateTime(new Date());
blog.setViews(1000);
mapper.addBlog(blog);
session.commit();
session.close();
}
初始化数据完毕!
1、编写接口
/根据作者名字和博客名字来查询博客!
//如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询
List querryBlogIf(Map map);
2、编写对应mapper文件
3、测试
@Test
public void testQuerryForIf(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
map.put("title" , "HTML如此简单");
map.put("author" , "李刚");
List blogs = mapper.querryBlogIf(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
这样写我们可以看到,如果 author 等于 null,那么查询语句为 select * from user where title=#{title},但是如果title为空呢?那么查询语句为 select * from user where and author=#{author},这是错误的 SQL 语句,如何解决呢?请看下面的 where 语句!
1、修改上面的sql后的
这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。
同理,上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?
1、编写接口方法
//更新博客
int updateBlog(Map map);
2、编写对应的mapper文件
update blog
title = #{title},
author = #{author},
where id = #{id}
3、测试
@Test
public void updateBlog(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
//map.put("title" , "Mybatis如此简单");
map.put("author" , "李刚");
map.put("id" ,"cf83426b6ea24c18881c5735f3a89f20");
mapper.updateBlog(map);
sqlSession.commit();
sqlSession.close();
}
有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句
1、编写接口方法
List querryBlogChoose(Map map);
2、编写对应的mapper配置文件
3、测试
@Test
public void testQuerryBlogChoose(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
map.put("title" , "Java如此简单");
map.put("author" , "李刚");
map.put("views" , "1000");
List blogs = mapper.querryBlogChoose(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
所谓的动态SQL,本质还是SQL语句,只是我们可以在SQL层面,去执行一个逻辑代码
!!提示:上面的测试可以更改参数来测试各个语句的真正功能(比如少传一个参数,多传一个)
有时候可能某个sql语句我们用的特别多,为了增强代码重用性,简化代码,我们学要将这些代码抽取出来,然后使用时直接调用。
提取SQL片段
title = #{title}
and author = #{author}
引用SQL片段
注意
将数据库中前三个数据的id修改为1、2、3
需求:我们需要查询blog表中id分为别为1、2、3的博客信息
1、编写接口
//foreach查询
List querryBlogForeach(Map map);
2、编写对应mapper配置文件
3、测试
@Test
public void testQuerryForeach(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
ArrayList ids = new ArrayList();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids" , ids);
List blogs = mapper.querryBlogForeach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合就可以了
建议
简介
1、什么是缓存 [ Cache ]?
2、为什么使用缓存?
3、什么样的数据能使用缓存?
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
一级缓存也叫本地缓存:
测试:1、在mybatis中加入日志,方便测试结果
2、编写接口方法
//根据id查询用户
User queryUserById(@Param("id") int id);
3、接口对应的Mapper文件
4、测试
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
结果分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JSdoTZ6Y-1596965436661)(C:\Users\litiangang\AppData\Roaming\Typora\typora-user-images\image-20200808193847046.png)]
一级缓存失效的四种情况
一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;
一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!
1、sqlSession不同
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
SqlSession session2 = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
session2.close();
}
观察结果:发现发送了两条SQL语句!
结论:每个sqlSession中的缓存相互独立
2、sqlSession相同,查询条件不同
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserMapper mapper2 = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper2.queryUserById(2);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
观察结果:发现发送了两条SQL语句!很正常的理解
结论:当前缓存中,不存在这个数据
3、sqlSession相同,两次查询之间执行了增删改操作!
增加方法
//修改用户
int updateUser(Map map);
编写SQL
update user set name = #{name} where id = #{id}
测试
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
HashMap map = new HashMap();
map.put("name","kuangshen");
map.put("id",4);
mapper.updateUser(map);
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
观察结果:查询在中间执行了增删改操作后,重新执行了
结论:因为增删改操作可能会对当前数据产生影响
4、sqlSession相同,手动清除一级缓存
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
session.clearCache();//手动清除缓存
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
一级缓存就是一个map
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
工作机制
使用步骤
1、开启全局缓存 【mybatis-config.xml】
2、去每个mapper.xml中配置使用二级缓存,这个配置非常简单;【xxxMapper.xml】
官方示例=====>查看官方文档
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
3、代码测试
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
SqlSession session2 = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
session.close();
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session2.close();
}
结论
缓存原理图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8nFoDMpK-1596965436666)(D:\DeskTop\缓存.png)]
第三方缓存实现–EhCache: 查看百度百科
Ehcache是一种广泛使用的java分布式缓存,用于通用缓存;
要在应用程序中使用Ehcache,需要引入依赖的jar包
org.mybatis.caches
mybatis-ehcache
1.1.0
在mapper.xml中使用对应的缓存即可
编写ehcache.xml文件,如果在加载时未找到/ehcache.xml资源或出现问题,则将使用默认配置。
ybatis.caches
mybatis-ehcache
1.1.0
在mapper.xml中使用对应的缓存即可
编写ehcache.xml文件,如果在加载时未找到/ehcache.xml资源或出现问题,则将使用默认配置。