一、 MyBatis(简化 Dao 层)
1.环境说明
- 环境:
- JDK 1.8
- MySQL 5.7/8.0:经典版本
- Maven 3.6.0/3.6.1
- IDEA :开发工具
- 回顾:
- JDBC : MyBatis 就是简化了 JDBC 操作
- MySQL :增删改查、事务
- Java 基础:封装继承的思想,看到一个东西尽量封装成工具类
- Maven :要会架构 Maven ,知道父子模块
- JUnit :单元测试
- 框架:
- 都是有配置文件的
- 最好的学习方式:看官网文档
2.简介
1.什么是 MyBatis ?
- MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射(方便写 SQL )。
- MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
- MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 实体类 【Plain Old Java Objects,普通的 Java对象】映射成数据库中的记录。
- MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。
- 2013年11月迁移到Github。
- iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)。
2.如何获得 MyBatis ?
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
-
GitHub :mybatis/mybatis-3: MyBatis SQL mapper framework for Java (github.com),点击 Releases 下载源码
3.持久化(动作)
- 数据持久化:数据库( JDBC )、文件 IO (处理文件,特别浪费资源)
- 持久层就是将程序的数据在持久状态(放到数据库里面,只要数据库不删库,数据就一直在)和瞬时状态(正常运行数据都在内存里,如果不持久化,内存假设出现任何意外,数据就丢了)转化的过程
- 为什么需要持久化?
- 内存有一个断电即失的特性,有一些对象不能丢掉(银行账号)
- 内存太贵了
4.持久层(概念)
- DAO 层( Data Access Object ,数据访问对象)、 Service 层(业务操作,调用 DAO 层)、 Controller 层(接收用户的请求,并且把用户的请求转发给下面的业务做)
- 完成持久化工作的代码块
- 层界限十分明显
5.为什么需要 MyBatis ?
- 帮助程序员将数据存入到数据库中和从数据库中取数据
- 方便
- 传统的 JDBC 代码太复杂了,简化,框架,自动化
- 不用 MyBatis 也可以,更容易上手。技术没有高低之分,只有使用这个技术的人有高低之别
- 优点(特性):
- 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件。易于学习,易于使用。通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
- 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
- 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的orm字段关系映射。
- 提供对象关系映射标签,支持对象关系组建维护。
- 提供xml标签,支持编写动态sql。
- 最重要的一点:使用的人多( Spring 、 SpringMVC 、 SpringBoot )
二、第一个 MyBatis 程序
- 思路:搭建环境–>导入 jar 包–>编写代码–>测试
-
搭建数据库
CREATE DATABASE `mybatis`; USE `mybatis`; DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` INT(20) NOT NULL, `name` VARCHAR(30) DEFAULT NULL, `pwd` VARCHAR(30) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=INNODB DEFAULT CHARSET=utf8; INSERT INTO `user`(`id`,`name`,`pwd`) VALUES (1,'张三','123456'),(2,'李四','abcdef'),(3,'王五','987654');
-
新建一个普通的 Maven 项目
-
导入相关依赖
<!--导入相关依赖--> <dependencies> <!--MySQL驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency> <!--junit单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
-
编写 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> <!--环境配置:可以配置多套环境(开发development、测试test……)--> <environments default="development"> <!--环境变量--> <environment id="development"> <!--事务管理器:默认使用JDBC事务管理--> <transactionManager type="JDBC"/> <!--数据源(连接池)--> <dataSource type="POOLED"> <!--数据库相关信息--> <property name="driver" value="com.mysql.jdbc.Driver"/> <!--在xml文件中&符号需要使用 & 转义--> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEcoding=utf8&useSSL=true"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!--映射器:每个Mapper.xml文件都需要在Mybatis核心配置文件中注册--> <mappers> <mapper resource="com/minami/dao/UserMapper.xml"/> </mappers> </configuration>
-
编写 mybatis 工具类
public class MyBatisUtil { private static SqlSessionFactory sqlSessionFactory; static { try { //1.从 XML 中构建 SqlSessionFactory // (也从 Java 代码而不是 XML 文件中创建配置,不推荐) //Maven项目可以直接获取resources下的资源 String resource = "mybatis-config.xml"; //读取配置文件加载成流(MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易) InputStream inputStream = Resources.getResourceAsStream(resource); //创建SqlSessionFactory工厂,使用工厂生产SqlSession对象 //每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的(工厂模式) //SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得(建造者模式) sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } //2.从 SqlSessionFactory 中获取 SqlSession //既然有了 SqlSessionFactory ,就可以从中获得 SqlSession 实例 //SqlSession 提供了在数据库执行 SQL 命令所需的所有方法 public static SqlSession getSqlSession() { return sqlSessionFactory.openSession(); } }
-
编写 User 实体类
public class User { private int id; private String name; private String pwd; public User() { } public User(int id, String name, String pwd) { this.id = id; this.name = name; this.pwd = pwd; } 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 getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", pwd='" + pwd + '\'' + '}'; } }
-
编写 UserMapper 接口
public interface UserMapper { //select:查询全部用户 List<User> findAll(); }
-
编写 UserMapper.xml 配置文件,由原来的 Mapper 接口实现类转变为一个 Mapper.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"> <!--namespace:命名空间,绑定一个对应的Dao/Mapper接口,名称为对应的Dao/Mapper接口的全类名--> <mapper namespace="com.minami.dao.UserMapper"> <!--select查询语句,id:方法名 resultType:返回类型,要写全类名,所有的集合都写泛型里面的东西--> <select id="findAll" resultType="com.minami.pojo.User"> select * from user </select> </mapper>
-
junit 测试,在 Maven 里面测试的包和开发的包一一对应
public class UserMapperTest {
@Test
public void test() {
//获取 SqlSession
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//执行 SQL 命令
//方式一:通过 SqlSession 实例来直接执行已映射的 SQL 语句,直接限定到方法,根据方法的返回值返回(已过时,不推荐使用)
// List<User> users = sqlSession.selectList("com.minami.dao.UserMapper.findAll");
//方式二:使用和指定语句的参数和返回值相匹配的接口,代码不仅更清晰,类型更加安全,还不用担心可能出错的字符串字面值以及强制类型转换。
//使用SqlSession创建Dao接口的代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//使用代理对象执行方法
List<User> users = mapper.findAll();
for (User user : users) {
System.out.println(user);
}
//释放资源
sqlSession.close();
}
}
问题总结:
-
配置文件没有注册:
org.apache.ibatis.binding.BindingException: Type interface com.minami.dao.UserMapper is not known to the MapperRegistry.
- 每个Mapper.xml文件都需要在Mybatis核心配置文件中注册
-
Maven 导出资源问题:
java.lang.ExceptionInInitializerError at com.minami.dao.UserMapperTest.test(UserMapperTest.java:14)
- Maven 静态资源过滤问题:Maven 项目默认资源文件配置都应该放在 resources 目录下,现在放在 java 目录下,是导出不了的,所以要手动配置资源过滤,可以让 src/main/java 下的 properties 或 xml 文件能够被导出,开启过滤
<!--在build中配置resources节点,来防止资源导出失败问题--> <build> <resources> <!--在src/main/resources文件夹下,properties和xml文件可以被导出--> <!--不写也可以,在src/main/resources文件夹下默认会被导出--> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> <!--在src/main/java文件夹下,properties和xml文件可以被导出--> <!--但是,在src/main/java文件夹下不能导出--> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
-
绑定接口错误
-
方法名不对
-
返回类型不对
三、 CRUD
-
所有的操作只跟 Mapper 接口和 Mapper.xml 配置文件有关
-
增删改需要提交事务
1.namespace
namespace
:命名空间,绑定一个对应的Dao/Mapper接口,名称为对应的Dao/Mapper接口的全类名
2.select
(查询)
id
:对应的namespace
中的方法名parameterType
:参数类型resultType
:返回类型, SQL 语句执行的返回值
-
在 UserMapper 中添加查询方法
public interface UserMapper { //select:查询全部用户 List<User> findAll(); //select:根据id查询用户 User getUserById(int id); }
-
在 UserMapper.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"> <!--namespace:命名空间,绑定一个对应的Dao/Mapper接口,名称为对应的Dao/Mapper接口的全类名--> <mapper namespace="com.minami.dao.UserMapper"> <!--select查询语句,id:方法名 resultType:返回类型,要写全类名,所有的集合都写泛型里面的东西--> <select id="findAll" resultType="com.minami.pojo.User"> select * from user </select> <select id="getUserById" parameterType="int" resultType="com.minami.pojo.User"> select * from user where id=#{id} </select> </mapper>
-
测试
@Test public void test() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //方式一:通过 SqlSession 实例来直接执行已映射的 SQL 语句,直接限定到方法,根据方法的返回值返回(已过时,不推荐使用) // List<User> users = sqlSession.selectList("com.minami.dao.UserMapper.findAll"); //方式二:使用和指定语句的参数和返回值相匹配的接口,代码不仅更清晰,类型更加安全,还不用担心可能出错的字符串字面值以及强制类型转换。 //使用SqlSession创建Dao接口的代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 List<User> users = mapper.findAll(); for (User user : users) { System.out.println(user); } //释放资源 sqlSession.close(); } @Test public void getUserById() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 User user = mapper.getUserById(1); System.out.println(user); //释放资源 sqlSession.close(); }
3.insert
(插入)
id
:对应的 namespace 中的方法名parameterType
:参数类型
-
在 UserMapper 中添加插入方法
//insert:添加一个用户 int addUser(User user);
-
在 UserMapper.xml 中添加插入语句
<!--对象中的属性可以直接取出来--> <insert id="addUser" parameterType="com.minami.pojo.User"> insert into user (id, name, pwd) values (#{id},#{name},#{pwd}); </insert>
-
测试
@Test public void addUser() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 mapper.addUser(new User(4, "赵六", "123456")); //增删改需要提交事务 sqlSession.commit(); //释放资源 sqlSession.close(); }
4.update
(修改)
id
:对应的 namespace 中的方法名parameterType
:参数类型
-
在 UserMapper 中添加修改方法
//update:修改一个用户 int updateUser(User user);
-
在 UserMapper.xml 中添加修改语句
<update id="updateUser" parameterType="com.minami.pojo.User"> update user set name=#{name},pwd=#{pwd} where id=#{id}; </update>
-
测试
@Test public void updateUser() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 mapper.updateUser(new User(4, "孙七", "123123")); //增删改需要提交事务 sqlSession.commit(); //释放资源 sqlSession.close(); }
5.delete
(删除)
id
:对应的 namespace 中的方法名parameterType
:参数类型
-
在 UserMapper 中添加删除方法
//delete:根据id删除用户 int deleteUserById(int id);
-
在 UserMapper.xml 中添加删除语句
<delete id="deleteUserById" parameterType="int"> delete from user where id=#{id} </delete>
-
测试
@Test public void deleteUserById() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 mapper.deleteUserById(4); //增删改需要提交事务 sqlSession.commit(); //释放资源 sqlSession.close(); }
6.问题总结
- 标签不要匹配错
- mybatis-config.xml 核心配置文件中 resource 绑定 mapper 需要使用路径
- 程序配置文件必须符合规范
- 没有注册到资源
- UserMapper.xml 中文出现乱码:把配置文件编码改成UTF8
- Maven 资源没有导出
7.万能Map
- 好处:不需要知道数据库里面有什么,只需要查对应的字段
- 实体类或者数据库中的表,字段或参数过多,应当考虑使用 Map
- Map 传递参数,直接在 SQL 中取出 key 即可
- 对象传递参数,直接在 SQL 中取对象的属性即可
- 只有一个基本类型参数的情况下,可以直接在 SQL 中取到(
parameterType
可以不写) - 多个参数用 Map 或者
@Param
注解
-
在 UserMapper 中添加对应的方法
//万能Map:添加一个用户 int addUserByMap(Map<String, Object> map); //万能Map:根据id查询用户 User getUserByMap(Map<String, Object> map);
-
在 UserMapper.xml 中添加对应的语句
<!--参数为map的key--> <insert id="addUserByMap" parameterType="map"> insert into user (id, name, pwd) values (#{userId},#{userName},#{userPassword}); </insert> <select id="getUserByMap" parameterType="map" resultType="com.minami.pojo.User"> select * from user where name=#{name} and pwd=#{pwd} </select>
-
测试
@Test public void addUserByMap() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map<String, Object> map = new HashMap<>(); map.put("userId", 5); map.put("userName", "田八"); map.put("userPassword", "123456"); //使用代理对象执行方法 mapper.addUserByMap(map); //增删改需要提交事务 sqlSession.commit(); //释放资源 sqlSession.close(); } @Test public void getUserByMap() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map<String, Object> map = new HashMap<>(); map.put("name", "张三"); map.put("pwd", "123456"); //使用代理对象执行方法 User user = mapper.getUserByMap(map); System.out.println(user); //释放资源 sqlSession.close(); }
8.like
(模糊查询)
-
在 UserMapper 中添加对应的方法
//模糊查询like:根据关键字查询用户 List<User> getUserByLike(String keywords);
-
在 UserMapper.xml 中添加对应的语句
<select id="getUserByLike" parameterType="string" resultType="com.minami.pojo.User"> select * from user where name like "%"+#{name}+"%" </select>
-
测试
@Test public void getUserByLike() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 //方式一:在Java代码中添加sql通配符 // List<User> users = mapper.getUserByLike("%张%"); //方式二:在sql语句中拼接通配符 List<User> users = mapper.getUserByLike("张"); for (User user : users) { System.out.println(user); } //释放资源 sqlSession.close(); }
四、 XML 配置解析( mybatis-config.xml 核心配置文件)
-
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。
-
在 mybatis-config.xml 核心配置文件中,所有的标签都可以规定其顺序:
"(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)".
-
configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
1.环境配置(environments)
- MyBatis 可以配置成适应多种环境
- 不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
- 学会使用配置多套运行环境
- MyBatis 默认的事务管理器就是 JDBC ,默认数据源类型是 POOLED
<!--环境配置:可以配置多套环境(开发development、测试test……)-->
<environments default="development">
<!--环境变量:开发环境-->
<environment id="development">
<!--事务管理器(JDBC/MANAGED):默认使用JDBC事务管理-->
<!--如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置-->
<transactionManager type="JDBC"/>
<!--数据源(UNPOOLED(没有池子)/POOLED(连接池)/JNDI(正常连接)):连接数据库-->
<!--UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但是性能要求不高,没有池(用完可以回收)的概念-->
<!--POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间,能使并发 Web 应用快速响应请求-->
<!--JNDI– 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用-->
<!--数据源也有很多第三方的实现,比如dbcp,c3p0,druid……-->
<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>
<!--环境变量:测试环境-->
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEcoding=utf8&useSSL=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
2.属性(properties)
- 可以通过 properties 属性来实现引用配置文件
- 这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。
-
创建一个外部 config.properties 配置文件
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEcoding=utf8&useSSL=true
-
在 mybatis-config.xml 核心配置文件中引入
<!--在xml中,所有的标签都可以规定其顺序--> <!--属性:引入外部配置文件--> <properties resource="config.properties"> <property name="username" value="root"/> <property name="password" value="root"/> </properties>
-
在 mybatis-config.xml 核心配置文件中替换需要动态配置的属性值
<dataSource type="POOLED"> <!--数据库相关信息--> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource>
- 可以直接引入外部配置文件
- 可以在其中增加一些属性配置
- 通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性
3.类型别名(typeAliases)
- 类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
<!--类型别名:可以给实体类起别名-->
<typeAliases>
<typeAlias type="com.minami.pojo.User" alias="User"/>
</typeAliases>
- 也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean
<!--类型别名:可以给实体类起别名-->
<typeAliases>
<!--在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名-->
<!--扫描实体类的包,默认别名就为这个类的类名(首字母小写,大写也可以)-->
<package name="com.minami.pojo"/>
</typeAliases>
- 在实体类比较少的时候,建议使用第一种;实体类十分多,建议使用第二种
- 第一种可以自定义别名,第二种则不行,若实体类上有
@Alias("user")
注解,则别名为其注解值
4.设置(settings)
- 这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled(开启全局缓存) | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
lazyLoadingEnabled(懒加载) | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | true | false | false |
mapUnderscoreToCamelCase(驼峰命名转换) | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true | false | False |
logImpl(日志实现) | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J(deprecated since 3.5.9) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
5.其他配置
- typeHandlers(类型处理器)
- MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。
- 你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。
- objectFactory(对象工厂)
- 每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。
- 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。
- 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。
- plugins(插件)
- MyBatis Generator Core:根据数据库的名字自动生成 MyBatis 代码的插件
- MyBatis Plus:是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
6.映射器(mappers)
- MapperRegistry :注册绑定 Mapper.xml 文件
- 既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括
file:///
形式的 URL),或类名和包名等。
- 方式一:使用相对于类路径的资源引用(推荐使用)
<!--映射器:每个Mapper.xml文件都需要在Mybatis核心配置文件中注册-->
<mappers>
<!--方式一:使用相对于类路径的资源引用(推荐使用)-->
<mapper resource="com/minami/dao/UserMapper.xml"/>
</mappers>
- 方式二:使用映射器接口实现类的完全限定类名(可以让注解和 xml 同时生效)
- Mapper 接口和它的 Mapper.xml 文件必须同名,并且在同一个包下
<!--映射器:每个Mapper.xml文件都需要在Mybatis核心配置文件中注册-->
<mappers>
<!--方式二:使用映射器接口实现类的完全限定类名-->
<mapper class="com.minami.dao.UserMapper"/>
</mappers>
- 方式三:将包内的映射器接口实现全部注册为映射器
- Mapper 接口和它的 Mapper.xml 文件必须同名,并且在同一个包下
<!--映射器:每个Mapper.xml文件都需要在Mybatis核心配置文件中注册-->
<mappers>
<!--方式三:将包内的映射器接口实现全部注册为映射器-->
<package name="com.minami.dao"/>
</mappers>
7.总结(完整的 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>
<!--在xml中,所有的标签都可以规定其顺序-->
<!--属性:引入外部配置文件-->
<properties resource="config.properties">
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
<!--类型别名:可以给实体类起别名-->
<typeAliases>
<!--类型别名:可以给实体类起别名-->
<!-- <typeAlias type="com.minami.pojo.User" alias="User"/>-->
<!--在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名-->
<!--扫描实体类的包,默认别名就为这个类的类名(首字母小写,大写也可以)-->
<package name="com.minami.pojo"/>
</typeAliases>
<!--环境配置:可以配置多套环境(开发development、测试test……)-->
<environments default="development">
<!--环境变量:开发环境-->
<environment id="development">
<!--事务管理器(JDBC/MANAGED):默认使用JDBC事务管理-->
<!--如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置-->
<transactionManager type="JDBC"/>
<!--数据源(UNPOOLED(没有池子)/POOLED(连接池)/JNDI(正常连接)):连接数据库-->
<!--UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但是性能要求不高,没有池(用完可以回收)的概念-->
<!--POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间,能使并发 Web 应用快速响应请求-->
<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>
<!--环境变量:测试环境-->
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEcoding=utf8&useSSL=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--映射器:每个Mapper.xml文件都需要在Mybatis核心配置文件中注册-->
<mappers>
<!--方式一:使用相对于类路径的资源引用(推荐使用)-->
<mapper resource="com/minami/dao/UserMapper.xml"/>
<!--方式二:使用映射器接口实现类的完全限定类名-->
<!-- <mapper class="com.minami.dao.UserMapper"/>-->
<!--方式三:将包内的映射器接口实现全部注册为映射器-->
<!-- <package name="com.minami.dao"/>-->
</mappers>
</configuration>
五、作用域(Scope)和生命周期
-
不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i5dx3crI-1672447453043)(https://cdn.jsdelivr.net/gh/327506144/picx-xpoet-cn@master/20210626/MyBatis执行流程.webp)]
- 这里的每一个SQL Mapper,就代表一个具体的业务
SqlSessionFactoryBuilder:(局部方法变量)
- 一旦创建了 SqlSessionFactory,就不再需要它了。
- SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(局部方法变量)。
SqlSessionFactory:(应用(全局)作用域)
- 可以想象成:数据库连接池
- 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
- 多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。
- SqlSessionFactory 的最佳作用域是应用(全局)作用域。
- 最简单的就是使用单例模式或者静态单例模式。
SqlSession:(请求或方法作用域)
- 连接到连接池的一个请求(需要开启和关闭)
- SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
- 考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。
- 用完之后需要赶紧关闭,否则资源被占用
六、resultMap (结果映射)
- 解决属性名和字段名不一致的问题:
-
新建一个 Maven 项目
-
创建实体类
public class User { private int id; //id private String name; //姓名 private String password; //密码和数据库不一样! public User() { } public User(int id, String name, String password) { this.id = id; this.name = name; this.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; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", password='" + password + '\'' + '}'; } }
-
在 UserMapper 中添加对应的方法
public interface UserMapper { //select:根据id查询用户 User getUserById(int id); }
-
在 UserMapper.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"> <!--namespace:命名空间,绑定一个对应的Dao/Mapper接口,名称为对应的Dao/Mapper接口的全类名--> <mapper namespace="com.minami.dao.UserMapper"> <select id="getUserById" parameterType="int" resultType="com.minami.pojo.User"> select * from user where id=#{id} </select> </mapper>
-
测试
@Test public void getUserById() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 User user = mapper.getUserById(1); System.out.println(user); //释放资源 sqlSession.close(); }
-
结果:User{id=1, name=‘张三’, password=‘null’}
-
分析:
-
select * from user where id = #{id} 可以看做
select id,name,pwd from user where id = #{id}
-
-
解决方法:
-
在 SELECT 语句中设置列别名:为列名指定别名 , 别名和java实体类的属性名一致
<select id="getUserById" parameterType="int" resultType="com.minami.pojo.User"> select id,name,pwd password from user where id=#{id} </select>
-
显式使用外部的
resultMap
(推荐使用)
<?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接口,名称为对应的Dao/Mapper接口的全类名--> <mapper namespace="com.minami.dao.UserMapper"> <!--结果映射--> <resultMap id="UserMap" type="User"> <!--column:数据库列名 property:实体类属性--> <result column="pwd" property="password"/> </resultMap> <!--在引用它的语句中设置 resultMap 属性就行了(注意去掉了 resultType 属性)--> <select id="getUserById" parameterType="int" resultMap="UserMap"> select * from user where id=#{id} </select> </mapper>
resultMap
元素是 MyBatis 中最重要最强大的元素。resultMap
的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。- HashMap 并不是一个很好的领域模型。你的程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为领域模型。
ResultMap
的优秀之处——你完全可以不用显式地配置它们。
-
-
七、日志
-
如果一个数据库操作,出现了异常,需要排错。日志就是最好的助手
-
以往的开发中,经常使用到 sout 输出,或者 debug 模式来调试,跟踪代码执行过程。
-
但是现在使用Mybatis是基于接口,配置文件的源代码执行过程。因此,我们必须选择日志工具来作为我们开发,调节程序的工具。
-
日志工厂
- SLF4J
- LOG4J
- LOG4J2 ( LOG4J 升级版)
- JDK_LOGGING ( Java 自带的日志输出)
- COMMONS_LOGGING (工具包)
- STDOUT_LOGGING (控制台输出,标准日志输出)
- NO_LOGGING (没有日志输出)
-
在 MyBatis 中具体使用哪一个日志实现,在设置中设定
1. STDOUT_LOGGING
-
在 mybatis-config.xml 核心配置文件中配置 STDOUT_LOGGING 日志输出
<!--设置--> <settings> <!--日志实现--> <!--标准的日志工厂实现--> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
-
控制台打印
//Logging初始化使用了StdOutImpl日志工厂的实现 Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter. //找一些类 Class not found: org.jboss.vfs.VFS JBoss 6 VFS API is not available in this environment. Class not found: org.jboss.vfs.VirtualFile VFS implementation org.apache.ibatis.io.JBoss6VFS is not valid in this environment. Using VFS adapter org.apache.ibatis.io.DefaultVFS //处理一些文件 Find JAR URL: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/pojo Not a JAR: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/pojo Reader entry: User.class Listing file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/pojo Find JAR URL: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/pojo/User.class Not a JAR: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/pojo/User.class Reader entry: ���� 1 < Checking to see if class com.minami.pojo.User matches criteria [is assignable to Object] PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. Find JAR URL: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/test-classes/com/minami/dao Not a JAR: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/test-classes/com/minami/dao Reader entry: UserMapperTest.class Listing file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/test-classes/com/minami/dao Find JAR URL: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/test-classes/com/minami/dao/UserMapperTest.class Not a JAR: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/test-classes/com/minami/dao/UserMapperTest.class Reader entry: ���� 1 9 Find JAR URL: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/dao Not a JAR: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/dao Reader entry: UserMapper.class Reader entry: UserMapper.xml Listing file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/dao Find JAR URL: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/dao/UserMapper.class Not a JAR: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/dao/UserMapper.class Reader entry: ���� 1 getUserById (I)Lcom/minami/pojo/User; Find JAR URL: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/dao/UserMapper.xml Not a JAR: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/dao/UserMapper.xml Reader entry: <?xml version="1.0" encoding="UTF-8" ?> Checking to see if class com.minami.dao.UserMapperTest matches criteria [is assignable to Object] Checking to see if class com.minami.dao.UserMapper matches criteria [is assignable to Object] //打开JDBC连接 Opening JDBC Connection //创建一个connection连接对象 Created connection 398110318. //设置自动提交为false Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@17baae6e] //预编译SQL ==> Preparing: select * from user where id=? //SQL要传的参数 ==> Parameters: 1(Integer) //查询的列 <== Columns: id, name, pwd //查询的记录 <== Row: 1, 张三, 123456 //查询的记录条数 <== Total: 1 //查询的结果 User{id=1, name='张三', password='123456'} //重置自动提交为true Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@17baae6e] //关闭Connection连接 Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@17baae6e] //把connection返回池子 Returned connection 398110318 to pool.
2. LOG4J
1.什么是 LOG4J ?
- Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
- 可以控制每一条日志的输出格式
- 通过定义每一条日志信息的级别,能够更加细致地控制日志的生成过程。
- 可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
2.配置
-
导入 log4j 依赖
<!--log4j--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
-
创建一个 config.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/log4j.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
-
在 mybatis-config.xml 核心配置文件中配置 LOG4J 日志实现
<!--设置--> <settings> <!--日志实现--> <setting name="logImpl" value="LOG4J"/> </settings>
-
在要输出日志的类中加入相关语句,使用测试
public class UserMapperTest { //在要输出日志的类中定义属性,生成日志对象,参数为当前类 static Logger logger = Logger.getLogger(UserMapperTest.class); @Test public void log4j() { //使用log4j //信息: logger.info("info:进入log4j方法"); //调试: logger.debug("debug:进入log4j方法"); //错误: logger.error("error: 进入log4j方法"); //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 User user = mapper.getUserById(1); System.out.println(user); //释放资源 sqlSession.close(); } }
-
日志输出(同时还生成了一个日志文件)
///Logging初始化使用了Log4jImpl日志工厂的实现 [org.apache.ibatis.logging.LogFactory]-Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter. [org.apache.ibatis.logging.LogFactory]-Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter. //找一些环境 [org.apache.ibatis.io.VFS]-Class not found: org.jboss.vfs.VFS [org.apache.ibatis.io.JBoss6VFS]-JBoss 6 VFS API is not available in this environment. [org.apache.ibatis.io.VFS]-Class not found: org.jboss.vfs.VirtualFile [org.apache.ibatis.io.VFS]-VFS implementation org.apache.ibatis.io.JBoss6VFS is not valid in this environment. [org.apache.ibatis.io.VFS]-Using VFS adapter org.apache.ibatis.io.DefaultVFS //处理一些文件 [org.apache.ibatis.io.DefaultVFS]-Find JAR URL: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/pojo [org.apache.ibatis.io.DefaultVFS]-Not a JAR: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/pojo [org.apache.ibatis.io.DefaultVFS]-Reader entry: User.class [org.apache.ibatis.io.DefaultVFS]-Listing file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/pojo [org.apache.ibatis.io.DefaultVFS]-Find JAR URL: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/pojo/User.class [org.apache.ibatis.io.DefaultVFS]-Not a JAR: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/pojo/User.class [org.apache.ibatis.io.DefaultVFS]-Reader entry: ���� 1 < [org.apache.ibatis.io.ResolverUtil]-Checking to see if class com.minami.pojo.User matches criteria [is assignable to Object] [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections. [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections. [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections. [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections. [org.apache.ibatis.io.DefaultVFS]-Find JAR URL: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/test-classes/com/minami/dao [org.apache.ibatis.io.DefaultVFS]-Not a JAR: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/test-classes/com/minami/dao [org.apache.ibatis.io.DefaultVFS]-Reader entry: UserMapperTest.class [org.apache.ibatis.io.DefaultVFS]-Listing file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/test-classes/com/minami/dao [org.apache.ibatis.io.DefaultVFS]-Find JAR URL: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/test-classes/com/minami/dao/UserMapperTest.class [org.apache.ibatis.io.DefaultVFS]-Not a JAR: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/test-classes/com/minami/dao/UserMapperTest.class [org.apache.ibatis.io.DefaultVFS]-Reader entry: ���� 1 9 [org.apache.ibatis.io.DefaultVFS]-Find JAR URL: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/dao [org.apache.ibatis.io.DefaultVFS]-Not a JAR: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/dao [org.apache.ibatis.io.DefaultVFS]-Reader entry: UserMapper.class [org.apache.ibatis.io.DefaultVFS]-Reader entry: UserMapper.xml [org.apache.ibatis.io.DefaultVFS]-Listing file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/dao [org.apache.ibatis.io.DefaultVFS]-Find JAR URL: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/dao/UserMapper.class [org.apache.ibatis.io.DefaultVFS]-Not a JAR: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/dao/UserMapper.class [org.apache.ibatis.io.DefaultVFS]-Reader entry: ���� 1 getUserById (I)Lcom/minami/pojo/User; [org.apache.ibatis.io.DefaultVFS]-Find JAR URL: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/dao/UserMapper.xml [org.apache.ibatis.io.DefaultVFS]-Not a JAR: file:/E:/%e7%ac%94%e8%ae%b0/JavaSE/23.mybatis-04-logger/target/classes/com/minami/dao/UserMapper.xml [org.apache.ibatis.io.DefaultVFS]-Reader entry: <?xml version="1.0" encoding="UTF-8" ?> [org.apache.ibatis.io.ResolverUtil]-Checking to see if class com.minami.dao.UserMapperTest matches criteria [is assignable to Object] [org.apache.ibatis.io.ResolverUtil]-Checking to see if class com.minami.dao.UserMapper matches criteria [is assignable to Object] //打开JDBC连接 [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection //创建一个connection连接对象 [org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 572191680. //设置自动提交为false [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@221af3c0] //预编译SQL [com.minami.dao.UserMapper.getUserById]-==> Preparing: select * from user where id=? //SQL要传的参数 [com.minami.dao.UserMapper.getUserById]-==> Parameters: 1(Integer) //查询的记录条数 [com.minami.dao.UserMapper.getUserById]-<== Total: 1 //查询的结果 User{id=1, name='张三', password='123456'} //重置自动提交为true [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@221af3c0] //关闭Connection连接 [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@221af3c0] //把connection返回池子 [org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 572191680 to pool.
八、分页
- 思考:为什么要分页?
- 减少数据的处理量,使数据库压力在可控范围内
1. limit 分页
--语法:起始下标=(当前页面-1)*页面大小,页面大小
SELECT * FROM table LIMIT stratIndex,pageSize
--从0开始,每页两个
select * from user limit 0,2;
--等价于limit 0,4
select * from user limit 4;
- 使用 MyBatis 实现分页,核心:SQL
-
在 UserMapper 中添加对应的方法
//通过 limit 分页查询用户 List<User> getUserByLimit(Map<String,Integer> map);
-
在 UserMapper.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"> <!--namespace:命名空间,绑定一个对应的Dao/Mapper接口,名称为对应的Dao/Mapper接口的全类名--> <mapper namespace="com.minami.dao.UserMapper"> <!--结果映射--> <resultMap id="UserMap" type="User"> <!--column:数据库的列名 property:实体类属性--> <result column="pwd" property="password"/> </resultMap> <!--在引用它的语句中设置 resultMap 属性就行了(注意我们去掉了 resultType 属性)--> <select id="getUserByLimit" parameterType="map" resultMap="UserMap"> select * from user limit #{stratIndex},#{pageSize} </select> </mapper>
-
测试
@Test public void getUserByLimit() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 Map<String,Integer> map = new HashMap<>(); map.put("stratIndex", 0); map.put("pageSize", 2); List<User> userList = mapper.getUserByLimit(map); for (User user : userList) { System.out.println(user); } //释放资源 sqlSession.close(); }
2. RowBounds 分页(不建议使用)
- 使用 Java 中的
RowBounds
类,面向对象没有 SQL 快
-
在 UserMapper 中添加对应的方法
//通过 RowBounds 分页查询用户 List<User> getUserByRowBounds();
-
在 UserMapper.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"> <!--namespace:命名空间,绑定一个对应的Dao/Mapper接口,名称为对应的Dao/Mapper接口的全类名--> <mapper namespace="com.minami.dao.UserMapper"> <!--结果映射--> <resultMap id="UserMap" type="User"> <!--column:数据库的列名 property:实体类属性--> <result column="pwd" property="password"/> </resultMap> <!--在引用它的语句中设置 resultMap 属性就行了(注意我们去掉了 resultType 属性)--> <select id="getUserByRowBounds" resultMap="UserMap"> select * from user </select> </mapper>
-
测试
@Test public void getUserByRowBounds() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //通过Java代码实现分页 RowBounds rowBounds = new RowBounds(0, 2); //通过 SqlSession 实例来直接执行已映射的 SQL 语句,直接限定到方法,根据方法的返回值返回(已过时,不推荐使用) List<User> userList = sqlSession.selectList("com.minami.dao.UserMapper.getUserByRowBounds", null, rowBounds); //使用代理对象执行方法 Map<String,Integer> map = new HashMap<>(); map.put("stratIndex", 0); map.put("pageSize", 2); for (User user : userList) { System.out.println(user); } //释放资源 sqlSession.close(); }
3. MyBatis 分页插件 PageHelper (了解即可)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ELoJhSI4-1672447453044)(https://cdn.jsdelivr.net/gh/327506144/picx-xpoet-cn@master/20210626/MyBatis分页插件PageHelper.webp)]
九、使用注解开发
1.面向接口编程
- 真正的开发中,很多时候会选择面向接口编程
- 根本原因:解耦,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好
- 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了
- 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。
关于接口的理解
- 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。
- 接口的本身反映了系统设计人员对系统的抽象理解。
- 接口应有两类:
- 对一个个体的抽象,它可对应为一个抽象体( abstract class )
- 对一个个体某一方面的抽象,即形成一个抽象面( interface )
- 一个个体有可能有多个抽象面。抽象体与抽象面是有区别的。
三个面向区别
- 面向对象:考虑问题时,以对象为单位,考虑它的属性及方法
- 面向过程:考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现
- 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题。更多的体现就是对系统整体的架构
2.使用注解开发
- 使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
- 注解可以跟配置文件一起用,保证在使用 class 映射的时候,接口和 Mapper.xml 文件在同一个包下,并且名字相同
- 本质:反射机制实现
- 底层:动态代理
-
编写 mybatis-config.xml 核心配置文件,绑定 UserMapper 接口
<!--映射器:每个Mapper.xml文件都需要在Mybatis核心配置文件中注册--> <mappers> <!--方式二:使用映射器接口实现类的完全限定类名--> <mapper class="com.minami.dao.UserMapper"/> </mappers>
-
在 UserMapper 中添加查询方法,注解在接口上实现
//select:查询全部用户 @Select("select * from user") List<User> findAll();
-
测试
@Test public void test() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 //底层主要应用反射 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 List<User> userList = mapper.findAll(); for (User user : userList) { System.out.println(user); } //释放资源 sqlSession.close(); }
3. MyBatis 的详细执行流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pclFNaxo-1672447453046)(https://cdn.jsdelivr.net/gh/327506144/picx-xpoet-cn@master/20210626/MyBatis的详细执行流程.webp)]
4. CRUD
-
必须要将接口注册绑定到核心配置文件中
<!--映射器:每个Mapper.xml文件都需要在Mybatis核心配置文件中注册--> <mappers> <!--方式二:使用映射器接口实现类的完全限定类名--> <mapper class="com.minami.dao.UserMapper"/> </mappers>
-
可以在工具类创建的时候实现自动提交事务
public class MyBatisUtil { private static SqlSessionFactory sqlSessionFactory; static { try { //1.从 XML 中构建 SqlSessionFactory // (也从 Java 代码而不是 XML 文件中创建配置,不推荐) //Maven项目可以直接获取resources下的资源 String resource = "mybatis-config.xml"; //读取配置文件加载成流(MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易) InputStream inputStream = Resources.getResourceAsStream(resource); //创建SqlSessionFactory工厂,使用工厂生产SqlSession对象 //每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的(工厂模式) //SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得(建造者模式) sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } //2.从 SqlSessionFactory 中获取 SqlSession //既然有了 SqlSessionFactory ,就可以从中获得 SqlSession 实例 //SqlSession 提供了在数据库执行 SQL 命令所需的所有方法 public static SqlSession getSqlSession() { //设置自动提交事务为true return sqlSessionFactory.openSession(true); } }
-
编写接口,增加注解
public interface UserMapper { //select:查询全部用户 @Select("select * from user") List<User> findAll(); //select:根据id查询用户, //方法存在多个参数,所有的参数前面必须加上@Param注解,引用类型不用加,如果只有一个基本类型可以忽略,建议加上 //在SQL中引用的参数就是@Param中设定的属性名 @Select("select * from user where id=#{userid} and name=#{name}") User getUserById(@Param("userid") int id, @Param("name") String name); //insert:添加一个用户 @Insert("insert into user (id, name, pwd) values (#{id},#{name},#{password})") int addUser(User user); //update:修改一个用户 @Update("update user set name=#{name},pwd=#{password} where id=#{id}") int updateUser(User user); //delete:根据id删除用户 @Delete("delete from user where id=#{id}") int deleteUserById(int id); }
-
测试
public class UserMapperTest { @Test public void test() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 //底层主要应用反射 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 List<User> userList = mapper.findAll(); for (User user : userList) { System.out.println(user); } //释放资源 sqlSession.close(); } @Test public void getUserById() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 //底层主要应用反射 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 User user = mapper.getUserById(1, "张三"); System.out.println(user); //释放资源 sqlSession.close(); } @Test public void addUser() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 //底层主要应用反射 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 mapper.addUser(new User(6, "李九", "123456")); //释放资源 sqlSession.close(); } @Test public void updateUser() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 //底层主要应用反射 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 mapper.updateUser(new User(6, "李九", "999999")); //释放资源 sqlSession.close(); } @Test public void deleteUserById() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 //底层主要应用反射 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 mapper.deleteUserById(6); //释放资源 sqlSession.close(); } }
- 问题:
${}
和#{}
的区别${}
:会直接进行拼接,无法防止 SQL 注入#{}
:防止 SQL 注入
十、 Lombok
Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java. Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.
- Project Lombok是一个java库,它会自动插入到你的编辑器和构建工具中,为你的java增添色彩。
再也不用编写另一个 getter 或 equals 方法,只需一个注释,您的类就会有一个功能齐全的构建器,自动执行日志记录变量等等。 - 常用注解
@Getter
and@Setter
:生成getter
和setter
方法@FieldNameConstants
:生成字段属性常量@ToString
:生成toString
方法@EqualsAndHashCode
:生成hashCode
和equals
方法@AllArgsConstructor
:生成包含类中所有字段的构造方法@NoArgsConstructor
:生成无参构造方法@Data
:生成无参构造方法、setter/getter、equals、canEqual、hashCode、toString方法@Accessors
:实现链式操作
1.使用步骤
-
在 IDEA 中安装 Lombok 插件
-
在项目中导入 Lombok 依赖
<!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency>
-
在实体类上加注解
@Data @NoArgsConstructor @AllArgsConstructor public class User { private int id; private String name; private String password; }
2.优缺点
-
优点:
- 能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,提高了一定的开发效率
- 让代码变得简洁,不用过多的去关注相应的方法
- 属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等
-
缺点:
- 不支持多种参数构造器的重载(可以手动加上)
- 虽然省去了手动创建getter/setter方法的麻烦,但大大降低了源代码的可读性和完整性,降低了阅读源代码的舒适度
十一、一对多和多对一
-
这两个概念就是一个相对的感觉
-
多对一:对于学生而言(
association
关联),多个学生关联一个老师 -
一对多:对于老师而言(
collection
集合),一个老师拥有多个学生
2.多对一(association
关联)
- 查询所有的学生信息,以及对应的老师的信息
1.环境搭建
-
数据库设计(多个学生对应一个老师)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XHSycWLz-1672447453049)(https://cdn.jsdelivr.net/gh/327506144/picx-xpoet-cn@master/20210626/多对一关系图.webp)]
-
创建数据库表
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');
-
导入依赖
<!--导入相关依赖--> <dependencies> <!--MySQL驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency> <!--junit单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency> </dependencies> <!--在build中配置resources节点,来防止资源导出失败问题--> <build> <resources> <!--在src/main/resources文件夹下,properties和xml文件可以被导出--> <!--不写也可以,在src/main/resources文件夹下默认会被导出--> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> <!--在src/main/java文件夹下,properties和xml文件可以被导出--> <!--但是,在src/main/java文件夹下不能导出--> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
-
创建一个 config.properties 配置文件
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEcoding=utf8&useSSL=true
-
在 mybatis-config.xml 核心配置文件中绑定注册 Mapper 接口或者文件( resources 文件夹和 java 文件夹同名生成的时候会自动合并)
<?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> <!--在xml中,所有的标签都可以规定其顺序--> <!--属性:引入外部配置文件--> <properties resource="config.properties"> <property name="username" value="root"/> <property name="password" value="root"/> </properties> <!--设置--> <settings> <!--日志实现--> <!--标准的日志工厂实现--> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <!--类型别名:可以给实体类起别名--> <typeAliases> <!-- <typeAlias type="com.minami.pojo.User" alias="User"/>--> <!--在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名--> <!--扫描实体类的包,默认别名就为这个类的类名(首字母小写,大写也可以)--> <package name="com.minami.pojo"/> </typeAliases> <!--环境配置:可以配置多套环境(开发development、测试test……)--> <environments default="development"> <!--环境变量:开发环境--> <environment id="development"> <!--事务管理器(JDBC/MANAGED):默认使用JDBC事务管理--> <!--如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置--> <transactionManager type="JDBC"/> <!--数据源(UNPOOLED(没有池子)/POOLED(连接池)/JNDI(正常连接)):连接数据库--> <!--UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但是性能要求不高,没有池(用完可以回收)的概念--> <!--POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间,能使并发 Web 应用快速响应请求--> <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> <!--环境变量:测试环境--> <environment id="test"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEcoding=utf8&useSSL=true"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!--映射器:每个Mapper.xml文件都需要在Mybatis核心配置文件中注册--> <mappers> <!--方式一:使用相对于类路径的资源引用(推荐使用)--> <mapper resource="com/minami/dao/TeacherMapper.xml"/> <mapper resource="com/minami/dao/StudentMapper.xml"/> </mappers> </configuration>
-
编写 mybatis 工具类
public class MyBatisUtil { private static SqlSessionFactory sqlSessionFactory; static { try { //1.从 XML 中构建 SqlSessionFactory // (也从 Java 代码而不是 XML 文件中创建配置,不推荐) //Maven项目可以直接获取resources下的资源 String resource = "mybatis-config.xml"; //读取配置文件加载成流(MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易) InputStream inputStream = Resources.getResourceAsStream(resource); //创建SqlSessionFactory工厂,使用工厂生产SqlSession对象 //每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的(工厂模式) //SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得(建造者模式) sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } //2.从 SqlSessionFactory 中获取 SqlSession //既然有了 SqlSessionFactory ,就可以从中获得 SqlSession 实例 //SqlSession 提供了在数据库执行 SQL 命令所需的所有方法 public static SqlSession getSqlSession() { //设置自动提交事务为true return sqlSessionFactory.openSession(true); } }
-
创建实体类
@Data @NoArgsConstructor @AllArgsConstructor public class Teacher { private int id; private String name; }
@Data @NoArgsConstructor @AllArgsConstructor public class Student { private int id; private String name; //学生要关联一个老师(组合):每个学生里面有个老师对象,学生就可以关联老师了 private Teacher teacher; }
-
编写实体类对应的 Mapper 接口(无论有没有需求,都应该写上,以备后来之需!)
public interface TeacherMapper { }
public interface StudentMapper { }
-
编写 Mapper 接口对应的 mapper.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"> <!--namespace:命名空间,绑定一个对应的Dao/Mapper接口,名称为对应的Dao/Mapper接口的全类名--> <mapper namespace="com.minami.dao.TeacherMapper"> </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接口,名称为对应的Dao/Mapper接口的全类名--> <mapper namespace="com.minami.dao.StudentMapper"> </mapper>
-
测试环境搭建是否成功
2.按照查询嵌套处理(子查询)
- 先查学生,再查老师,由于学生里面的结果存在老师这样一个特殊的对象,而不是一个普通的字段,使用
association
单独处理,association
的column
和property
是固定的,只不过增加了javaType
和select
,由于老师是一个对象,要通过javaType
给对象设置一个类型,对象通过select
查出来老师
-
编写 TeacherMapper 接口
public interface StudentMapper { //查询所有的学生信息,以及对应的老师的信息 //按照查询嵌套处理 List<Student> getStudent(); }
-
编写 TeacherMapper.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"> <!--namespace:命名空间,绑定一个对应的Dao/Mapper接口,名称为对应的Dao/Mapper接口的全类名--> <mapper namespace="com.minami.dao.StudentMapper"> <!--思路: 1.查询所有的学生信息 2.根据查询出来的学生的tid,寻找对应的老师的信息 --> <!--方式一:按照查询嵌套处理(子查询)--> <select id="getStudent" resultMap="StudentTeacher"> select * from student </select> <resultMap id="StudentTeacher" type="Student"> <!--id:主键--> <id column="id" property="id"/> <!--result:其它字段--> <result column="name" property="name"/> <!--复杂的属性,需要单独处理 对象:association 集合:collection--> <!--column:数据库字段 property:实体类属性 javaType:Java类型 select:嵌套查询,下一条要执行的SQL--> <!--column:在上次查询结果集中,用哪些列作为条件去执行下一条SQL语句 property:注入给实体类的某个属性 javaType:把SQL语句查询出的结果集,封装给某个类的对象(可以省略) select:下一条要执行的SQL语句--> <association column="tid" property="teacher" javaType="Teacher" select="getTeacher"/> </resultMap> <!--查询老师的信息,参数会自动匹配--> <select id="getTeacher" resultType="Teacher"> select * from teacher where id=#{id} </select> </mapper>
-
测试
@Test public void getStudent(){ //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 //底层主要应用反射 StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); //使用代理对象执行方法 List<Student> students = mapper.getStudent(); for (Student student : students) { System.out.println(student); } //释放资源 sqlSession.close(); }
-
结果
==> Preparing: select * from student ==> Parameters: <== Columns: id, name, tid <== Row: 1, 小明, 1 ====> Preparing: select * from teacher where id=? ====> Parameters: 1(Integer) <==== Columns: id, name <==== Row: 1, 秦老师 <==== Total: 1 <== Row: 2, 小红, 1 <== Row: 3, 小张, 1 <== Row: 4, 小李, 1 <== Row: 5, 小王, 1 <== Total: 5 Student(id=1, name=小明, teacher=Teacher(id=1, name=秦老师)) Student(id=2, name=小红, teacher=Teacher(id=1, name=秦老师)) Student(id=3, name=小张, teacher=Teacher(id=1, name=秦老师)) Student(id=4, name=小李, teacher=Teacher(id=1, name=秦老师)) Student(id=5, name=小王, teacher=Teacher(id=1, name=秦老师))
3.按照结果嵌套处理(联表查询)
- 查询 SQL 写完之后,只需要对应里面的每一项关系
-
编写 TeacherMapper 接口
public interface StudentMapper { //查询所有的学生信息,以及对应的老师的信息 //按照结果嵌套处理 List<Student> getStudent2(); }
-
编写 TeacherMapper.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"> <!--namespace:命名空间,绑定一个对应的Dao/Mapper接口,名称为对应的Dao/Mapper接口的全类名--> <mapper namespace="com.minami.dao.StudentMapper"> <!--思路: 1.查询所有的学生信息 2.根据查询出来的学生的tid,寻找对应的老师的信息 --> <!--方式二:按照结果嵌套处理(联表查询)--> <select id="getStudent2" resultMap="StudentTeacher2"> select s.id sid,s.name sname,t.name tname from student s,teacher t where s.tid = t.id; </select> <!--实体类的结果--> <resultMap id="StudentTeacher2" type="Student"> <!--id:主键--> <id column="sid" property="id"/> <!--result:其它字段--> <result column="sname" property="name"/> <!--复杂的属性,需要单独处理 对象:association 集合:collection--> <!--column:查询出来的结果 property:实体类属性 javaType:Java类型 select:下一条要执行的SQL--> <!--column:在上次查询结果集中,用哪些列作为条件去执行下一条SQL语句 property:注入给实体类的某个属性 javaType:把SQL语句查询出的结果集,封装给某个类的对象(可以省略) select:下一条要执行的SQL语句--> <association property="teacher" javaType="Teacher"> <result column="tname" property="name"/> </association> </resultMap> </mapper>
-
测试
@Test public void getStudent2(){ //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 //底层主要应用反射 StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); //使用代理对象执行方法 List<Student> students = mapper.getStudent2(); for (Student student : students) { System.out.println(student); } //释放资源 sqlSession.close(); }
-
结果
==> Preparing: select s.id sid,s.name sname,t.name tname from student s,teacher t where s.tid = t.id; ==> Parameters: <== Columns: sid, sname, tname <== Row: 1, 小明, 秦老师 <== Row: 2, 小红, 秦老师 <== Row: 3, 小张, 秦老师 <== Row: 4, 小李, 秦老师 <== Row: 5, 小王, 秦老师 <== Total: 5 Student(id=1, name=小明, teacher=Teacher(id=0, name=秦老师)) Student(id=2, name=小红, teacher=Teacher(id=0, name=秦老师)) Student(id=3, name=小张, teacher=Teacher(id=0, name=秦老师)) Student(id=4, name=小李, teacher=Teacher(id=0, name=秦老师)) Student(id=5, name=小王, teacher=Teacher(id=0, name=秦老师))
3.一对多(collection
集合)
- 获取指定老师下的所有学生及老师的信息
1.环境搭建
-
导入依赖
<!--导入相关依赖--> <dependencies> <!--MySQL驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency> <!--junit单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency> </dependencies> <!--在build中配置resources节点,来防止资源导出失败问题--> <build> <resources> <!--在src/main/resources文件夹下,properties和xml文件可以被导出--> <!--不写也可以,在src/main/resources文件夹下默认会被导出--> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> <!--在src/main/java文件夹下,properties和xml文件可以被导出--> <!--但是,在src/main/java文件夹下不能导出--> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
-
创建一个 config.properties 配置文件
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEcoding=utf8&useSSL=true
-
在 mybatis-config.xml 核心配置文件中绑定注册 Mapper 接口或者文件( resources 文件夹和 java 文件夹同名生成的时候会自动合并)
<?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> <!--在xml中,所有的标签都可以规定其顺序--> <!--属性:引入外部配置文件--> <properties resource="config.properties"> <property name="username" value="root"/> <property name="password" value="root"/> </properties> <!--设置--> <settings> <!--日志实现--> <!--标准的日志工厂实现--> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <!--类型别名:可以给实体类起别名--> <typeAliases> <!-- <typeAlias type="com.minami.pojo.User" alias="User"/>--> <!--在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名--> <!--扫描实体类的包,默认别名就为这个类的类名(首字母小写,大写也可以)--> <package name="com.minami.pojo"/> </typeAliases> <!--环境配置:可以配置多套环境(开发development、测试test……)--> <environments default="development"> <!--环境变量:开发环境--> <environment id="development"> <!--事务管理器(JDBC/MANAGED):默认使用JDBC事务管理--> <!--如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置--> <transactionManager type="JDBC"/> <!--数据源(UNPOOLED(没有池子)/POOLED(连接池)/JNDI(正常连接)):连接数据库--> <!--UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但是性能要求不高,没有池(用完可以回收)的概念--> <!--POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间,能使并发 Web 应用快速响应请求--> <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> <!--环境变量:测试环境--> <environment id="test"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEcoding=utf8&useSSL=true"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!--映射器:每个Mapper.xml文件都需要在Mybatis核心配置文件中注册--> <mappers> <!--方式一:使用相对于类路径的资源引用(推荐使用)--> <mapper resource="com/minami/dao/TeacherMapper.xml"/> <mapper resource="com/minami/dao/StudentMapper.xml"/> </mappers> </configuration>
-
编写 mybatis 工具类
public class MyBatisUtil { private static SqlSessionFactory sqlSessionFactory; static { try { //1.从 XML 中构建 SqlSessionFactory // (也从 Java 代码而不是 XML 文件中创建配置,不推荐) //Maven项目可以直接获取resources下的资源 String resource = "mybatis-config.xml"; //读取配置文件加载成流(MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易) InputStream inputStream = Resources.getResourceAsStream(resource); //创建SqlSessionFactory工厂,使用工厂生产SqlSession对象 //每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的(工厂模式) //SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得(建造者模式) sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } //2.从 SqlSessionFactory 中获取 SqlSession //既然有了 SqlSessionFactory ,就可以从中获得 SqlSession 实例 //SqlSession 提供了在数据库执行 SQL 命令所需的所有方法 public static SqlSession getSqlSession() { //设置自动提交事务为true return sqlSessionFactory.openSession(true); } }
-
创建实体类
@Data @NoArgsConstructor @AllArgsConstructor public class Teacher { private int id; private String name; //一个老师拥有多个学生(学生集合) private List<Student> students; }
@Data @NoArgsConstructor @AllArgsConstructor public class Student { private int id; private String name; private int tid; }
-
编写实体类对应的 Mapper 接口(无论有没有需求,都应该写上,以备后来之需!)
public interface TeacherMapper { }
public interface StudentMapper { }
-
编写 Mapper 接口对应的 mapper.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"> <!--namespace:命名空间,绑定一个对应的Dao/Mapper接口,名称为对应的Dao/Mapper接口的全类名--> <mapper namespace="com.minami.dao.TeacherMapper"> </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接口,名称为对应的Dao/Mapper接口的全类名--> <mapper namespace="com.minami.dao.StudentMapper"> </mapper>
-
测试环境搭建是否成功
2.按照结果嵌套处理(联表查询)
-
编写 TeacherMapper 接口
public interface TeacherMapper { //获取指定老师下的所有学生及老师的信息 //按照结果嵌套处理 Teacher getTeacherById(@Param("tid") int id); }
-
编写 TeacherMapper.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"> <!--namespace:命名空间,绑定一个对应的Dao/Mapper接口,名称为对应的Dao/Mapper接口的全类名--> <mapper namespace="com.minami.dao.TeacherMapper"> <!--方式一:按照结果嵌套处理(联表查询)--> <select id="getTeacherById" resultMap="TeacherStudent"> select s.id sid,s.name sname,t.name tname,t.id tid from student s,teacher t where s.tid = t.id and t.id=#{tid}; </select> <resultMap id="TeacherStudent" type="Teacher"> <!--id:主键--> <id column="tid" property="id"/> <!--result:其它字段--> <result column="tname" property="name"/> <!--复杂的属性,需要单独处理 对象:association 集合:collection--> <!--ofType:获取集合中的泛型信息--> <collection property="students" ofType="Student"> <!--id:主键--> <id column="sid" property="id"/> <!--result:其它字段--> <result column="sname" property="name"/> <result column="tid" property="tid"/> </collection> </resultMap> </mapper>
-
测试
@Test public void getTeacher(){ //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 //底层主要应用反射 TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); //使用代理对象执行方法 Teacher teacher = mapper.getTeacherById(1); System.out.println(teacher); //释放资源 sqlSession.close(); }
-
结果
==> Preparing: select s.id sid,s.name sname,t.name tname,t.id tid from student s,teacher t where s.tid = t.id and t.id=?; ==> Parameters: 1(Integer) <== Columns: sid, sname, tname, tid <== Row: 1, 小明, 秦老师, 1 <== Row: 2, 小红, 秦老师, 1 <== Row: 3, 小张, 秦老师, 1 <== Row: 4, 小李, 秦老师, 1 <== Row: 5, 小王, 秦老师, 1 <== Total: 5 Teacher(id=1, name=秦老师, students=[Student(id=1, name=小明, tid=1), Student(id=2, name=小红, tid=1), Student(id=3, name=小张, tid=1), Student(id=4, name=小李, tid=1), Student(id=5, name=小王, tid=1)])
3.按照查询嵌套处理(子查询)
-
编写 TeacherMapper 接口
public interface TeacherMapper { //获取指定老师下的所有学生及老师的信息 //按照查询嵌套处理 Teacher getTeacherById2(@Param("tid") int id); }
-
编写 TeacherMapper.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"> <!--namespace:命名空间,绑定一个对应的Dao/Mapper接口,名称为对应的Dao/Mapper接口的全类名--> <mapper namespace="com.minami.dao.TeacherMapper"> <!--方式二:按照查询嵌套处理(子查询)--> <select id="getTeacherById2" resultMap="TeacherStudent2"> select * from teacher where id=#{tid} </select> <resultMap id="TeacherStudent2" type="Teacher"> <!--id:主键--> <id column="id" property="id"/> <!--result:其它字段--> <result column="name" property="name"/> <!--复杂的属性,需要单独处理 对象:association 集合:collection--> <!--column:数据库字段 property:实体类属性 javaType:Java类型 ofType:获取集合中的泛型信息 select:嵌套查询,下一条要执行的SQL--> <!--column:在上次查询结果集中,用哪些列作为条件去执行下一条SQL语句 property:注入给实体类的某个属性 javaType:把SQL语句查询出的结果集,封装给某个类的对象(可以省略) select:下一条要执行的SQL语句--> <collection column="id" property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId"/> </resultMap> <!--通过老师id获取学生信息--> <select id="getStudentByTeacherId" resultType="Student"> select * from student where tid=#{tid} </select> </mapper>
-
测试
@Test public void getTeacher2(){ //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 //底层主要应用反射 TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); //使用代理对象执行方法 Teacher teacher = mapper.getTeacherById2(1); System.out.println(teacher); //释放资源 sqlSession.close(); }
-
结果
==> Preparing: select * from teacher where id=? ==> Parameters: 1(Integer) <== Columns: id, name <== Row: 1, 秦老师 ====> Preparing: select * from student where tid=? ====> Parameters: 1(Integer) <==== Columns: id, name, tid <==== Row: 1, 小明, 1 <==== Row: 2, 小红, 1 <==== Row: 3, 小张, 1 <==== Row: 4, 小李, 1 <==== Row: 5, 小王, 1 <==== Total: 5 <== Total: 1 Teacher(id=1, name=秦老师, students=[Student(id=1, name=小明, tid=1), Student(id=2, name=小红, tid=1), Student(id=3, name=小张, tid=1), Student(id=4, name=小李, tid=1), Student(id=5, name=小王, tid=1)])
4.总结
- 多对一(
association
关联) - 一对多(
collection
集合) javaType
:用来指定实体类中属性的类型ofType
:用来指定映射到 List 或者集合中的 POJO 类型,泛型中的约束类型- 注意点:
- 保证 SQL 的可读性,尽量保证通俗易懂
- 注意一对多和多对一中,属性名和字段的问题
- 如果问题不好排查错误,可以使用日志,建议使用 log4j
- 面试高频:
- Mysql 引擎
- InnoDB 底层原理
- 索引
- 索引优化
十二、动态 SQL
- 什么是动态 SQL :根据不同的条件生成不同的 SQL 语句(本质还是 SQL 语句,只是可以在 SQL 层面执行一些逻辑代码)
- 利用动态 SQL,可以彻底摆脱根据不同条件拼接 SQL 语句的痛苦。
- 如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
- 动态 SQL 就是在拼接 SQL语句,只要保证 SQL 的正确性,按照 SQL 的格式,去排列组合就可以了
- 建议:先写出完整的 SQL ,再对应的去修改成为动态 SQL 实现通用即可!
1.搭建环境
-
创建数据库表
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
-
导入依赖
<!--导入相关依赖--> <dependencies> <!--MySQL驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency> <!--junit单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency> </dependencies> <!--在build中配置resources节点,来防止资源导出失败问题--> <build> <resources> <!--在src/main/resources文件夹下,properties和xml文件可以被导出--> <!--不写也可以,在src/main/resources文件夹下默认会被导出--> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> <!--在src/main/java文件夹下,properties和xml文件可以被导出--> <!--但是,在src/main/java文件夹下不能导出--> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
-
创建一个 config.properties 配置文件
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEcoding=utf8&useSSL=true
-
在 mybatis-config.xml 核心配置文件中绑定注册 Mapper 接口或者文件( resources 文件夹和 java 文件夹同名生成的时候会自动合并)
<?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> <!--在xml中,所有的标签都可以规定其顺序--> <!--属性:引入外部配置文件--> <properties resource="config.properties"> <property name="username" value="root"/> <property name="password" value="root"/> </properties> <!--设置--> <settings> <!--日志实现--> <!--标准的日志工厂实现--> <setting name="logImpl" value="STDOUT_LOGGING"/> <!--开启驼峰命名自动映射--> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> <!--类型别名:可以给实体类起别名--> <typeAliases> <!-- <typeAlias type="com.minami.pojo.User" alias="User"/>--> <!--在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名--> <!--扫描实体类的包,默认别名就为这个类的类名(首字母小写,大写也可以)--> <package name="com.minami.pojo"/> </typeAliases> <!--环境配置:可以配置多套环境(开发development、测试test……)--> <environments default="development"> <!--环境变量:开发环境--> <environment id="development"> <!--事务管理器(JDBC/MANAGED):默认使用JDBC事务管理--> <!--如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置--> <transactionManager type="JDBC"/> <!--数据源(UNPOOLED(没有池子)/POOLED(连接池)/JNDI(正常连接)):连接数据库--> <!--UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但是性能要求不高,没有池(用完可以回收)的概念--> <!--POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间,能使并发 Web 应用快速响应请求--> <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> <!--环境变量:测试环境--> <environment id="test"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEcoding=utf8&useSSL=true"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!--映射器:每个Mapper.xml文件都需要在Mybatis核心配置文件中注册--> <mappers> <!--方式二:使用映射器接口实现类的完全限定类名--> <mapper class="com.minami.dao.BlogMapper"/> </mappers> </configuration>
-
编写 mybatis 工具类
public class MyBatisUtil { private static SqlSessionFactory sqlSessionFactory; static { try { //1.从 XML 中构建 SqlSessionFactory // (也从 Java 代码而不是 XML 文件中创建配置,不推荐) //Maven项目可以直接获取resources下的资源 String resource = "mybatis-config.xml"; //读取配置文件加载成流(MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易) InputStream inputStream = Resources.getResourceAsStream(resource); //创建SqlSessionFactory工厂,使用工厂生产SqlSession对象 //每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的(工厂模式) //SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得(建造者模式) sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } //2.从 SqlSessionFactory 中获取 SqlSession //既然有了 SqlSessionFactory ,就可以从中获得 SqlSession 实例 //SqlSession 提供了在数据库执行 SQL 命令所需的所有方法 public static SqlSession getSqlSession() { //设置自动提交事务为true return sqlSessionFactory.openSession(true); } }
-
编写生成 UUID 工具类
public class IdUtil { public static String getId() { //去除uuid中的- return UUID.randomUUID().toString().replaceAll("-", ""); } }
-
创建实体类
@Data @NoArgsConstructor @AllArgsConstructor public class Blog { private String id; private String title; private String author; private Date createTime; //Java属性名和数据库字段名不一致,需要在核心配置文件开启驼峰命名自动映射 private int views; }
-
编写实体类对应的 Mapper 接口(无论有没有需求,都应该写上,以备后来之需!)
public interface BlogMapper { }
-
编写 Mapper 接口对应的 mapper.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"> <!--namespace:命名空间,绑定一个对应的Dao/Mapper接口,名称为对应的Dao/Mapper接口的全类名--> <mapper namespace="com.minami.dao.BlogMapper"> </mapper>
-
测试环境搭建是否成功
public class BlogTest { }
-
插入数据
//插入数据 int addBlog(Blog blog);
<insert id="addBlog" parameterType="blog"> insert into blog (id, title, author, create_time, views) values (#{id}, #{title}, #{author}, #{createTime}, #{views}); </insert>
@Test public void addBlog(){ //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 //底层主要应用反射 BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); Blog blog = new Blog(); blog.setId(IdUtil.getId()); blog.setTitle("Mybatis如此简单"); blog.setAuthor("张三"); blog.setCreateTime(new Date()); blog.setViews(999); //使用代理对象执行方法 mapper.addBlog(blog); blog.setId(IdUtil.getId()); blog.setTitle("Spring如此简单"); blog.setViews(100); mapper.addBlog(blog); blog.setId(IdUtil.getId()); blog.setTitle("SpringMVC如此简单"); blog.setViews(999); mapper.addBlog(blog); //释放资源 sqlSession.close(); }
2. if
-
在 BlogMapper 中添加方法
//if:查询博客 List<Blog> findAllByIf(Map map);
-
在 BlogMapper.xml 中添加语句
<select id="findAllByIf" parameterType="map" resultType="blog"> select * from blog where 1=1 <if test="title != null"> and title=#{title} </if> <if test="author != null"> and author=#{author} </if> </select>
-
测试
@Test public void findAllByIf(){ //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 //底层主要应用反射 BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, Object> map = new HashMap<>(); //博客标题和博客作者都为空:select * from blog where 1=1 //博客标题和博客作者不为空:select * from blog where 1=1 and title='Mybatis如此简单' and author='张三' //博客标题不为空:select * from blog where 1=1 and title='Mybatis如此简单' //博客作者不为空:select * from blog where 1=1 and author='张三' map.put("title", "Mybatis如此简单"); map.put("author", "张三"); //使用代理对象执行方法 List<Blog> blogs = mapper.findAllByIf(map); for (Blog blog : blogs) { System.out.println(blog); } //释放资源 sqlSession.close(); }
3. where
- where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
-
在 BlogMapper 中添加方法
//where:查询博客 List<Blog> findAllByWhere(Map map);
-
在 BlogMapper.xml 中添加语句
<select id="findAllByWhere" parameterType="map" resultType="blog"> select * from blog <where> <if test="title != null"> title=#{title} </if> <if test="author != null"> and author=#{author} </if> </where> </select>
-
测试
@Test public void findAllByWhere(){ //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 //底层主要应用反射 BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, Object> map = new HashMap<>(); //博客标题和博客作者都为空:select * from blog //博客标题和博客作者不为空:select * from blog where title='Mybatis如此简单' and author='张三' //博客标题不为空:select * from blog where title='Mybatis如此简单' //博客作者不为空:select * from blog where author='张三' map.put("title", "Mybatis如此简单"); map.put("author", "张三"); //使用代理对象执行方法 List<Blog> blogs = mapper.findAllByWhere(map); for (Blog blog : blogs) { System.out.println(blog); } //释放资源 sqlSession.close(); }
4. choose(switch)、when(case)、otherwise(default)
- 不想使用所有的条件,而只是想从多个条件中选择一个使用。有点像 Java 中的 switch 语句。
-
在 BlogMapper 中添加方法
//choose:查询博客 List<Blog> findAllByChoose(Map map);
-
在 BlogMapper.xml 中添加语句
<select id="findAllByChoose" parameterType="map" resultType="blog"> select * from 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>
-
测试
@Test public void findAllByChoose(){ //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 //底层主要应用反射 BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, Object> map = new HashMap<>(); //博客标题和博客作者和浏览量都为空:select * from blog WHERE views='' //博客标题不为空:select * from blog where title='Mybatis如此简单' //博客作者不为空:select * from blog where author='张三' //浏览量不为空:select * from blog where views=100 //只会选择其中的一个去实现 //博客标题和浏览量不为空:select * from blog where title='Mybatis如此简单' map.put("title", "Mybatis如此简单"); map.put("views", 100); //使用代理对象执行方法 List<Blog> blogs = mapper.findAllByChoose(map); for (Blog blog : blogs) { System.out.println(blog); } //释放资源 sqlSession.close(); }
5. set
- set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
-
在 BlogMapper 中添加方法
//set:更新博客 int updateBlogBySet(Map map);
-
在 BlogMapper.xml 中添加语句
<update id="updateBlogBySet" parameterType="map"> update blog <set> <if test="title != null"> title=#{title}, </if> <if test="author != null"> author=#{author} </if> </set> where id=#{id} </update>
-
测试
@Test public void updateBlogBySet(){ //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 //底层主要应用反射 BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, Object> map = new HashMap<>(); //博客标题和博客作者不为空:update blog SET title='Mybatis如此简单', author='张三' where id='c0546d4af8a2448696629db07de05068' //博客标题和博客作者都为空(语句不完整,报错):update blog where id=? //博客标题不为空:update blog SET title='Mybatis如此简单' where id='c0546d4af8a2448696629db07de05068' //博客作者不为空:update blog SET author='张三' where id='c0546d4af8a2448696629db07de05068' map.put("title", "Mybatis如此简单"); // map.put("author", "张三"); map.put("id", "c0546d4af8a2448696629db07de05068"); //使用代理对象执行方法 mapper.updateBlogBySet(map); //释放资源 sqlSession.close(); }
6. trim
-
prefix
:前缀 -
prefixOverrides
:前缀覆盖,会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的) -
会移除所有
prefixOverrides
属性中指定的内容,并且插入 prefix 属性中指定的内容。 -
suffix
:后缀 -
suffixOverrides
:后缀覆盖<!--和 where 元素等价的自定义 trim 元素:会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容--> <trim prefix="WHERE" prefixOverrides="AND |OR "> ... </trim> <!--与 set 元素等价的自定义 trim 元素:--> <trim prefix="SET" suffixOverrides=","> ... </trim>
7. sql 代码片段
-
在 Mybatis 文档 XML 映射器 中可以找到 sql 代码片段
-
用来定义可重用的 SQL 代码片段,以便在其它语句中使用。(将一些公共的部分抽取出来,方便复用)
-
在 BlogMapper 中添加方法
//sql:查询博客 List<Blog> findAllBySql(Map map);
-
在 BlogMapper.xml 中添加语句
<!--1.使用sql标签抽取公共部分--> <sql id="if-title-author"> <if test="title != null"> title=#{title} </if> <if test="author != null"> and author=#{author} </if> </sql> <select id="findAllBySql" parameterType="map" resultType="blog"> select * from blog <where> <!--2.在需要使用的地方使用include标签引用 refid:引用sql标签id--> <include refid="if-title-author"></include> </where> </select>
-
测试
@Test public void findAllBySql(){ //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 //底层主要应用反射 BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, Object> map = new HashMap<>(); //博客标题和博客作者都为空:select * from blog //博客标题和博客作者不为空:select * from blog where title='Spring如此简单' and author='张三' //博客标题不为空:select * from blog where title='Spring如此简单' //博客作者不为空:select * from blog where author='张三' map.put("title", "Spring如此简单"); map.put("author", "张三"); //使用代理对象执行方法 List<Blog> blogs = mapper.findAllBySql(map); for (Blog blog : blogs) { System.out.println(blog); } //释放资源 sqlSession.close(); }
- 注意:
- 最好基于单表来定义 sql 片段( sql 片段里面不要做一些太复杂的事情,因为复杂的事情重用的效率就变低了)
- 不要存在
where
或set
标签(会优化 sql )
8. foreach
- 对集合进行遍历(尤其是在构建 IN 条件语句的时候)。
- foreach 元素的功能非常强大,它允许你指定一个集合(collection),声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头(open)与结尾(close)的字符串以及集合项迭代之间的分隔符(separator)。这个元素也不会错误地添加多余的分隔符
-
在 BlogMapper 中添加方法
//foreach:查询id为c0546d4af8a2448696629db07de05068和650fb79666a34e44a6dde9798c937fe1的博客 List<Blog> findAllByForeach(Map map);
-
在 BlogMapper.xml 中添加语句
<select id="findAllByForeach" parameterType="map" resultType="blog"> select * from blog <where> <foreach collection="ids" item="id" open="and (" separator="or" close=")"> id=#{id} </foreach> </where> </select>
-
测试
@Test public void findAllByForeach(){ //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 //底层主要应用反射 BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); //select * from blog where 1=1 and (id=1 or id=2 or id=3); HashMap<String, Object> map = new HashMap<>(); List<String> ids = new ArrayList<>(); ids.add("c0546d4af8a2448696629db07de05068"); ids.add("650fb79666a34e44a6dde9798c937fe1"); map.put("ids", ids); //使用代理对象执行方法 //传递一个万能的map,这个map可以存一个集合 List<Blog> blogs = mapper.findAllByForeach(map); for (Blog blog : blogs) { System.out.println(blog); } //释放资源 sqlSession.close(); }
十三、缓存
- 问题:查询——连接数据库——耗资源
- 解决:一次查询的结果,暂存在一个可以直接取到的地方(内存),放在内存里面查询到的数据就叫缓存
- 再次查询相同数据的时候,直接走缓存,就不用走数据库了
1.简介
1.什么是缓存(Cache)?
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
2.为什么使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统效率
3.什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据
4.什么样的数据不使用缓存?
- 不经常查询并且经常改变的数据
2. Mybatis 缓存
-
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。缓存可以极大的提升查询效率
-
Mybatis 系统中默认定义了两级缓存:一级缓存和二级缓存
-
默认情况下,只有一级缓存开启。( SqlSession 级别的缓存(一次会话,用完会返回池子),也称为本地缓存)
-
二级缓存需要手动开启和配置,它是基于 namespace 级别的缓存(一个接口,可以在 Mapper 接口里面的所有方法用到,级别更高了一点)
-
为了提高扩展性, Mybatis 定义了缓存接口 Cache 。我们可以通过实现 Cache 接口来自定义二级缓存
public interface Cache { String getId(); //放一些值 void putObject(Object var1, Object var2); //获取一些值 Object getObject(Object var1); //移除一些值 Object removeObject(Object var1); //清理一些值 void clear(); //获取缓存大小 int getSize(); default ReadWriteLock getReadWriteLock() { return null; } }
-
可用的清除策略有:(默认的清除策略是 LRU )(缓存有限,不可能无限的缓存,缓存到达一定的量就开始清理缓存)
LRU
– 最近最少使用:移除最长时间不被使用的对象。FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
3.搭建环境
-
导入依赖
<!--导入相关依赖--> <dependencies> <!--MySQL驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency> <!--junit单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency> </dependencies> <!--在build中配置resources节点,来防止资源导出失败问题--> <build> <resources> <!--在src/main/resources文件夹下,properties和xml文件可以被导出--> <!--不写也可以,在src/main/resources文件夹下默认会被导出--> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> <!--在src/main/java文件夹下,properties和xml文件可以被导出--> <!--但是,在src/main/java文件夹下不能导出--> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
-
创建一个 config.properties 配置文件
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEcoding=utf8&useSSL=true
-
在 mybatis-config.xml 核心配置文件中绑定注册 Mapper 接口或者文件( resources 文件夹和 java 文件夹同名生成的时候会自动合并)
<?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> <!--在xml中,所有的标签都可以规定其顺序--> <!--属性:引入外部配置文件--> <properties resource="config.properties"> <property name="username" value="root"/> <property name="password" value="root"/> </properties> <!--设置--> <settings> <!--日志实现--> <!--标准的日志工厂实现--> <setting name="logImpl" value="STDOUT_LOGGING"/> <!--开启驼峰命名自动映射--> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> <!--类型别名:可以给实体类起别名--> <typeAliases> <!-- <typeAlias type="com.minami.pojo.User" alias="User"/>--> <!--在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名--> <!--扫描实体类的包,默认别名就为这个类的类名(首字母小写,大写也可以)--> <package name="com.minami.pojo"/> </typeAliases> <!--环境配置:可以配置多套环境(开发development、测试test……)--> <environments default="development"> <!--环境变量:开发环境--> <environment id="development"> <!--事务管理器(JDBC/MANAGED):默认使用JDBC事务管理--> <!--如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置--> <transactionManager type="JDBC"/> <!--数据源(UNPOOLED(没有池子)/POOLED(连接池)/JNDI(正常连接)):连接数据库--> <!--UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但是性能要求不高,没有池(用完可以回收)的概念--> <!--POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间,能使并发 Web 应用快速响应请求--> <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> <!--环境变量:测试环境--> <environment id="test"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEcoding=utf8&useSSL=true"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!--映射器:每个Mapper.xml文件都需要在Mybatis核心配置文件中注册--> <mappers> <!--方式二:使用映射器接口实现类的完全限定类名--> <mapper class="com.minami.dao.UserMapper"/> </mappers> </configuration>
-
编写 mybatis 工具类
public class MyBatisUtil { private static SqlSessionFactory sqlSessionFactory; static { try { //1.从 XML 中构建 SqlSessionFactory // (也从 Java 代码而不是 XML 文件中创建配置,不推荐) //Maven项目可以直接获取resources下的资源 String resource = "mybatis-config.xml"; //读取配置文件加载成流(MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易) InputStream inputStream = Resources.getResourceAsStream(resource); //创建SqlSessionFactory工厂,使用工厂生产SqlSession对象 //每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的(工厂模式) //SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得(建造者模式) sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } //2.从 SqlSessionFactory 中获取 SqlSession //既然有了 SqlSessionFactory ,就可以从中获得 SqlSession 实例 //SqlSession 提供了在数据库执行 SQL 命令所需的所有方法 public static SqlSession getSqlSession() { //设置自动提交事务为true return sqlSessionFactory.openSession(true); } }
-
创建实体类
@Data @NoArgsConstructor @AllArgsConstructor public class User { private int id; //id private String name; //姓名 private String pwd; //密码 }
-
编写实体类对应的 Mapper 接口(无论有没有需求,都应该写上,以备后来之需!)
public interface UserMapper { }
-
编写 Mapper 接口对应的 mapper.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"> <!--namespace:命名空间,绑定一个对应的Dao/Mapper接口,名称为对应的Dao/Mapper接口的全类名--> <mapper namespace="com.minami.dao.UserMapper"> </mapper>
-
测试环境搭建是否成功
public class UserTest { }
4.一级缓存(本地缓存)
- 与数据库同一次会话期间查询到的数据会放在本地缓存中
- 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库
-
在 mybatis-config.xml 核心配置文件中开启日志,方便测试
<!--标准的日志工厂实现--> <setting name="logImpl" value="STDOUT_LOGGING"/>
-
在 UserMapper 中添加方法
public interface UserMapper { //根据id查询用户 User getUserById(int id); }
-
在 UserMapper.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"> <!--namespace:命名空间,绑定一个对应的Dao/Mapper接口,名称为对应的Dao/Mapper接口的全类名--> <mapper namespace="com.minami.dao.UserMapper"> <select id="getUserById" parameterType="int" resultType="com.minami.pojo.User"> select * from user where id=#{id} </select> </mapper>
-
测试在一个
SqlSession
中查询两次相同记录@Test public void cache() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 //查询id=1的用户 User user = mapper.getUserById(1); System.out.println(user); System.out.println(""); //查询id=1的用户 User user2 = mapper.getUserById(1); System.out.println(user2); System.out.println(user==user2); //释放资源 sqlSession.close(); }
-
查看日志输出
//打开连接 Opening JDBC Connection //创建连接池 Created connection 1413378318. //下面的步骤都在一次会话SqlSession里面 //SQL语句只查询了一次 ==> Preparing: select * from user where id=? ==> Parameters: 1(Integer) <== Columns: id, name, pwd <== Row: 1, 张三, 123456 <== Total: 1 User(id=1, name=张三, pwd=123456) //第二次结果,没有进行数据库查询 User(id=1, name=张三, pwd=123456) //并且两个对象相等,从缓存里面取的,用的是同一个对象 true //关闭连接 Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@543e710e] //返回连接池 Returned connection 1413378318 to pool.
一级缓存失效的情况:
-
查询不同的东西
@Test public void cache1() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 //查询id=1的用户 User user = mapper.getUserById(1); System.out.println(user); System.out.println(""); //查询id=2的用户 User user2 = mapper.getUserById(2); System.out.println(user2); System.out.println(user==user2); //释放资源 sqlSession.close(); }
//打开连接 Opening JDBC Connection //创建连接池 Created connection 1413378318 //查询id=1的用户 ==> Preparing: select * from user where id=? ==> Parameters: 1(Integer) <== Columns: id, name, pwd <== Row: 1, 张三, 123456 <== Total: 1 User(id=1, name=张三, pwd=123456) //查询id=2的用户 ==> Preparing: select * from user where id=? ==> Parameters: 2(Integer) <== Columns: id, name, pwd <== Row: 2, 李四, abcdef <== Total: 1 User(id=2, name=李四, pwd=abcdef) false //关闭连接 Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@543e710e] //返回连接池 Returned connection 1413378318 to pool.
-
映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。(增删改操作,可能会改变原来的数据,所以必定会刷新缓存)
@Test public void cache2() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 //查询id=1的用户 User user = mapper.getUserById(1); System.out.println(user); //增加用户 mapper.addUser(new User(6, "张三", "123456")); System.out.println(""); //查询id=1的用户 User user2 = mapper.getUserById(1); System.out.println(user2); System.out.println(user==user2); //释放资源 sqlSession.close(); }
//打开连接 Opening JDBC Connection //创建连接池 Created connection 1413378318. //查询id=1的用户 ==> Preparing: select * from user where id=? ==> Parameters: 1(Integer) <== Columns: id, name, pwd <== Row: 1, 张三, 123456 <== Total: 1 User(id=1, name=张三, pwd=123456) //增加用户 ==> Preparing: insert into user (id, name, pwd) values (?, ?, ?); ==> Parameters: 6(Integer), 张三(String), 123456(String) <== Updates: 1 //查询id=1的用户 ==> Preparing: select * from user where id=? ==> Parameters: 1(Integer) <== Columns: id, name, pwd <== Row: 1, 张三, 123456 <== Total: 1 User(id=1, name=张三, pwd=123456) false //关闭连接 Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@543e710e] //返回连接池 Returned connection 1413378318 to pool.
-
查询不同的 Mapper.xml (不同的 Mapper 里面连二级缓存都不存在,更别说一级缓存了)
-
手动清理缓存
@Test public void cache4() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 //查询id=1的用户 User user = mapper.getUserById(1); System.out.println(user); //手动清理缓存 sqlSession.clearCache(); System.out.println(""); //查询id=1的用户 User user2 = mapper.getUserById(1); System.out.println(user2); System.out.println(user==user2); //释放资源 sqlSession.close(); }
//打开连接 Opening JDBC Connection //创建连接池 Created connection 1413378318. //第一次查询 ==> Preparing: select * from user where id=? ==> Parameters: 1(Integer) <== Columns: id, name, pwd <== Row: 1, 张三, 123456 <== Total: 1 User(id=1, name=张三, pwd=123456) //手动清理缓存之后,第二次查询 ==> Preparing: select * from user where id=? ==> Parameters: 1(Integer) <== Columns: id, name, pwd <== Row: 1, 张三, 123456 <== Total: 1 User(id=1, name=张三, pwd=123456) false //关闭连接 Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@543e710e] //返回连接池 Returned connection 1413378318 to pool.
小结:
- 一级缓存默认是开启的也关闭不了,只在一次
SqlSession
中有效,也就是打开连接到关闭连接之间 - 一级缓存就是一个
Map
(用的时候往 Map 里面放,取的时候从 Map 里面取)
5.二级缓存
-
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
-
基于
namespace
级别的缓存,一个名称空间,对应一个二级缓存 -
工作机制:
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果当前会话关闭了,这个会话对应的一级缓存就没了,但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
- 新的会话查询信息,就可以从二级缓存中获取内容
- 不同的 mapper 查出的数据会放在自己对应的缓存(map)中
-
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
<cache/>
-
在 mybatis-config.xml 核心配置文件中开启全局缓存
<!--标准的日志工厂实现--> <setting name="logImpl" value="STDOUT_LOGGING"/> <!--显示开启全局缓存,默认开启--> <setting name="cacheEnabled" value="true"/>
-
在要使用二级缓存的 UserMapper.xml 中开启二级缓存
<!--在当前Mapper.xml中使用二级缓存--> <cache/>
也可以自定义参数
<!--在当前Mapper.xml中使用二级缓存--> <!--创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突--> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
-
测试
@Test public void l2Cache() { //获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); SqlSession sqlSession2 = MyBatisUtil.getSqlSession(); //执行 SQL 命令 //使用SqlSession创建Dao接口的代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用代理对象执行方法 User user = mapper.getUserById(1); System.out.println(user); //释放资源 sqlSession.close(); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); User user2 = mapper2.getUserById(1); System.out.println(user2); sqlSession2.close(); }
-
关闭二级缓存日志输出
//打开连接 Opening JDBC Connection //创建连接池 Created connection 1413378318. //第一次查询 ==> Preparing: select * from user where id=? ==> Parameters: 1(Integer) <== Columns: id, name, pwd <== Row: 1, 张三, 123456 <== Total: 1 User(id=1, name=张三, pwd=123456) //关闭连接 Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@543e710e] //返回连接池 Returned connection 1413378318 to pool. //打开连接 Opening JDBC Connection //创建连接池 Checked out connection 1413378318 from pool. //第二次查询 ==> Preparing: select * from user where id=? ==> Parameters: 1(Integer) <== Columns: id, name, pwd <== Row: 1, 张三, 123456 <== Total: 1 User(id=1, name=张三, pwd=123456) false //关闭连接 Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@543e710e] //返回连接池 Returned connection 1413378318 to pool.
-
开启二级缓存日志输出
//打开连接 Opening JDBC Connection //创建连接池 Created connection 1413378318. //第一次查询 ==> Preparing: select * from user where id=? ==> Parameters: 1(Integer) <== Columns: id, name, pwd <== Row: 1, 张三, 123456 <== Total: 1 User(id=1, name=张三, pwd=123456) //关闭连接 Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@543e710e] //返回连接池 Returned connection 1413378318 to pool. //缓存命中率:0.5 Cache Hit Ratio [com.minami.dao.UserMapper]: 0.5 //第二次查询 User(id=1, name=张三, pwd=123456) true
-
问题:需要将实体类序列化,否则就报错
java.io.NotSerializableException: com.minami.pojo.User
-
小结:
- 只要开启了二级缓存,在同一个 Mapper 下就有效
- 所有的数据都会先放在一级缓存中
- 只有当前会话提交,或者关闭的时候,才会提交到二级缓存中
6.缓存原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4UT8x4LF-1672447453051)(https://cdn.jsdelivr.net/gh/327506144/picx-xpoet-cn@master/20210626/Mybatis缓存原理.png)]
7.自定义缓存(ehcache)
- EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
- Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存
-
导入依赖
<!--ehcache--> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.2</version> </dependency>
-
在 UserMapper.xml 中指定使用 ehcache 缓存
<!--在当前Mapper.xml中指定使用 ehcache 缓存--> <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
-
编写 ehcache.xml 文件
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false"> <!-- diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下: user.home – 用户主目录 user.dir – 用户当前工作目录 java.io.tmpdir – 默认临时文件路径 --> <diskStore path="./tmpdir/Tmp_EhCache"/> <defaultCache eternal="false" maxElementsInMemory="10000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="259200" memoryStoreEvictionPolicy="LRU"/> <cache name="cloud_user" eternal="false" maxElementsInMemory="5000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" memoryStoreEvictionPolicy="LRU"/> <!-- defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。 --> <!-- name:缓存名称。 maxElementsInMemory:缓存最大数目 maxElementsOnDisk:硬盘最大缓存个数。 eternal:对象是否永久有效,一但设置了,timeout将不起作用。 overflowToDisk:是否保存到磁盘,当系统宕机时 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 clearOnFlush:内存数量最大时是否清除。 memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。 FIFO,first in first out,这个是大家最熟的,先进先出。 LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。 LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。 --> </ehcache>
- Radis 数据库做缓存,把缓存放到K-V键值对中