0、环境说明
0.1、环境说明
- IDEA、jdk 8 +、maven-3.6.1、MySQL 5.7.19
0.2、知识回顾
- Java 基础、Maven、MySQL、JDBC、Junit
0.3、数据库导入和导出
-- 导出
1. 导出一张表 -- mysqldump -uroot -p123456 school student >D:/a.sql
mysqldump -u用户名 -p密码 库名 表名 > 文件名(D:/a.sql)
2. 导出多张表 -- mysqldump -uroot -p123456 school student result >D:/a.sql
mysqldump -u用户名 -p密码 库名 表1 表2 表3 > 文件名(D:/a.sql)
3. 导出所有表 -- mysqldump -uroot -p123456 school >D:/a.sql
mysqldump -u用户名 -p密码 库名 > 文件名(D:/a.sql)
4. 导出一个库 -- mysqldump -uroot -p123456 -B school >D:/a.sql
mysqldump -u用户名 -p密码 -B 库名 > 文件名(D:/a.sql)
-- 导入
1. 在登录mysql的情况下:-- source D:/a.sql
source 备份文件
2. 在不登录的情况下
mysql -u用户名 -p密码 库名 < 备份文件
1、简介
1.1、什么是Mybatis
-
MyBatis 是一款优秀的持久层框架;
-
它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
-
maven仓库:
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency>
1.2、持久化
-
数据持久化
- 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
- 内存:断电即失
- 数据库(Jdbc),io文件持久化。
-
为什么要持久化?
- 有一些对象,不能让他丢掉
- 内存太贵
1.3、持久层
- Dao层、Service层、Controller层
- 完成持久化工作的代码块
- 层界限十分明显
1.4、为什么需要MyBatis
-
帮助程序员将数据存入到数据库中
-
方便
-
传统的JDBC代码太复杂了,简化,框架,自动化
-
不用MyBatis也可以,更容易上手,技术没有高低之分
-
优点:(百度百科)
- 简单易学
- 灵活
- sql和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的orm字段关系映射
- 提供对象关系映射标签,支持对象关系组建维护
- 提供xml标签,支持编写动态sql
2、第一个Mybatis程序
思路:搭建环境 --> 配置MyBatis --> 编写代码 --> 测试
2.1、搭建环境
- 搭建数据库
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,'cwlin','123456'),(2,'张三','abcdef'),(3,'李四','987654');
-
新建项目
-
创建一个普通的maven项目
-
删除src目录 (就可以把此工程当做父工程了,然后创建子工程)
-
导入maven依赖
<!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.23</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
-
创建一个Module
-
2.2、创建一个模块
-
编写mybatis的核心配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <!--configuration核心配置文件--> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!--每一个Mapper.xml都需要在MyBatis核心配置文件中注册!在后续测试中遇到问题时,注册以下mappers即可--> <!--<mappers> <mapper resource="com/cwlin/dao/UserMapper.xml"/> </mappers>--> </configuration>
-
编写mybatis工具类
package com.cwlin.utils; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; //sqlSessionFactory --> sqlSession public class MybatisUtils { static SqlSessionFactory sqlSessionFactory = null; static { try { //使用Mybatis第一步:获取sqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } //既然有了SqlSessionFactory,顾名思义,我们可以从中获得SqlSession的实例. //SqlSession提供了在数据库执行SQL命令所需的所有方法。 public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
2.3、编写代码
-
实体类(Pojo层)
package com.cwlin.pojo; //实体类 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 + '\'' + '}'; } }
-
Dao接口(Dao层)
package com.cwlin.dao; import com.cwlin.pojo.User; import java.util.List; public interface UserMapper { //查询全部用户 List<User> getUserList(); }
-
接口实现类(由原来的UserDaoImplement转变为一个Mapper配置文件)
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--namespace=绑定一个指定的Dao/Mapper接口--> <mapper namespace="com.cwlin.dao.UserDao"> <!--select查询语句--> <select id="getUserList" resultType="com.cwlin.pojo.User"> select * from mybatis.user </select> </mapper>
2.4、测试
-
注意点(1)
- org.apache.ibatis.binding.BindingException: Type interface com.kuang.dao.UserDao is not known to the MapperRegistry.
-
MapperRegistry是什么?
-
在mybatis的核心配置文件中,注册mappers。
<!--每一个Mapper.xml都需要在MyBatis核心配置文件中注册!--> <mappers> <mapper resource="com/cwlin/dao/UserMapper.xml"/> </mappers>
-
-
注意点(2)
- java.io.IOException: Could not find resource com/cwlin/dao/UserMapper.xml
-
为什么找不到UserMapper.xml?
-
在父工程的pom.xml中,配置resources;如果仍是不行,在子工程中也加上配置。
<!--在build中配置resources,来防止我们资源导出失败的问题--> <build> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
-
-
junit测试
public class UserDaoTest { @Test //推荐使用 public void test(){ //1.获取SqlSession对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); //2.执行SQL //方式一:getMapper UserDao userDao = sqlSession.getMapper(UserDao.class); List<User> userList = userDao.getUserList(); //方式二:selectList/selectMap/selectOne 不推荐使用 //List<User> userList = sqlSession.selectList("com.cwlin.dao.UserDao.getUserList"); for (User user : userList) { System.out.println(user); } //3.关闭sqlSession sqlSession.close(); } @Test //以下是官方建议模式,了解即可,只是建议! public void recommendedTest(){ try (SqlSession sqlSession = MybatisUtils.getSqlSession()) { UserDao userDao = sqlSession.getMapper(UserDao.class); List<User> userList = userDao.getUserList(); for (User user : userList) { System.out.println(user); } } } }
-
可能会遇到的问题:
- 配置文件没有注册
- 绑定接口错误
- 方法名不对
- 返回类型不对
- Maven导出资源问题
3、CURD(增删改查)
3.1、namespace
- namespace中的包名要和Dao/Mapper接口的包名一致
- 在以后的学习中,将UserDao接口重命名为UserMapper接口!
3.2、select、Insert、update、Delete
-
在配置好之后,CURD只需要在dao层下的的
Mapper接口类
里面加方法,在Mapper.xml
文件里加标签,在测试类
里加方法即可。 -
Mapper接口的标签:
- id:就是对应的namespace中的方法名;
- resultType:Sql语句执行的返回值;
-
parameterType:参数类型;
-
实现步骤:
-
编写接口
package com.cwlin.dao; import com.cwlin.pojo.User; import java.util.List; import java.util.Map; public interface UserMapper { //查询全部用户 List<User> getUserList(); //根据id查询用户 User getUserById(int id); //插入用户 int insertUser(User user); //更新用户 int updateUser(User user); //删除用户 int deleteUser(int id); }
-
编写对应的mapper中的sql语句
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--namespace=绑定一个指定的Dao/Mapper接口--> <mapper namespace="com.cwlin.dao.UserMapper"> <!--select查询语句--> <select id="getUserList" resultType="com.cwlin.pojo.User"> select * from mybatis.user </select> <select id="getUserById" parameterType="int" resultType="com.cwlin.pojo.User"> select * from mybatis.user where id = #{id} </select> <insert id="insertUser" parameterType="com.cwlin.pojo.User"> insert into mybatis.user(id, name, pwd) values (#{id}, #{name}, #{pwd}); </insert> <update id="updateUser" parameterType="com.cwlin.pojo.User"> update mybatis.user set name = #{name}, pwd = #{pwd} where id = #{id}; </update> <delete id="deleteUser" parameterType="int"> delete from mybatis.user where id = #{id} </delete> </mapper>
-
测试
package com.cwlin.dao; import com.cwlin.pojo.User; import com.cwlin.utils.MybatisUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import java.util.List; public class UserMapperTest { @Test //推荐使用 public void test(){ //1.获取SqlSession对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); //2.执行SQL //方式一:getMapper UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> userList = userMapper.getUserList(); //方式二:selectList/selectMap/selectOne 不推荐使用 //List<User> userList = sqlSession.selectList("com.cwlin.dao.UserDao.getUserList"); for (User user : userList) { System.out.println(user); } //3.关闭sqlSession sqlSession.close(); } @Test //以下是官方建议模式,了解即可,只是建议! public void recommendedTest(){ try (SqlSession sqlSession = MybatisUtils.getSqlSession()) { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> userList = userMapper.getUserList(); for (User user : userList) { System.out.println(user); } } } @Test public void getUserById() { SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.getUserById(1); System.out.println(user); sqlSession.close(); } @Test public void insertUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); int res = mapper.insertUser(new User(4, "王五", "654321")); if(res > 0){ System.out.println("插入成功!"); } //提交事务 sqlSession.commit(); sqlSession.close(); } @Test public void updateUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); int res = mapper.updateUser(new User(4, "王五", "666666")); if(res > 0){ System.out.println("更新成功!"); } //提交事务 sqlSession.commit(); sqlSession.close(); } @Test public void deleteUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); int res = mapper.deleteUser(4); if(res > 0){ System.out.println("删除成功!"); } //提交事务 sqlSession.commit(); sqlSession.close(); } }
-
-
注意:增删改(查)一定要提交事务:
sqlSession.commit();
-
错误分析:
- 标签不能匹配错误
- resource必须绑定mapper,需要使用路径
- 程序配置文件必须符合规范
- NullPointerException,没有注册到资源
- maven资源没有导出问题
- xml文件中存在中文乱码问题
3.3、万能Map
-
假设,我们的实体类,或者数据库中的表,字段或者参数过多,我们应该考虑使用Map!
-
实现步骤:
-
UserMapper接口
package com.cwlin.dao; import com.cwlin.pojo.User; import java.util.List; import java.util.Map; public interface UserMapper { //万能的Map类 User getUserById2(Map<String,Object> map); int insertUser2(Map<String,Object> 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接口--> <mapper namespace="com.cwlin.dao.UserMapper"> <select id="getUserById2" parameterType="map" resultType="com.cwlin.pojo.User"> select * from mybatis.user where id = #{id} and name = #{name} </select> <insert id="insertUser2" parameterType="map"> insert into mybatis.user(id, name, pwd) values (#{userId}, #{userName}, #{password}); </insert> </mapper>
-
测试
package com.cwlin.dao; import com.cwlin.pojo.User; import com.cwlin.utils.MybatisUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import java.util.HashMap; import java.util.List; import java.util.Map; public class UserMapperTest { @Test public void getUserById2() { SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map<String, Object> map = new HashMap<>(); map.put("id", 1); map.put("name", "cwlin"); User user = mapper.getUserById2(map); System.out.println(user); sqlSession.close(); } @Test public void insertUser2(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map<String, Object> map = new HashMap<>(); map.put("userId", 4); map.put("userName", "王五"); map.put("password", "654321"); int res = mapper.insertUser2(map); if(res > 0){ System.out.println("插入成功!"); } //提交事务 sqlSession.commit(); sqlSession.close(); } }
-
-
注意事项
- Map传递参数,直接在sql中取出key即可! 【parameter=“map”】
- 对象传递参数,直接在sql中取出对象的属性即可! 【parameter=“Object”】
- 只有一个基本类型参数的情况下,可以直接在sql中取到,可以省略参数类型标签parameterType!
- 多个参数用Map , 或者注解!
3.4、模糊查询
-
两种方法
-
Java代码执行的时候,传递通配符% %
@Test public void getUserLike() { SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.getUserLike("%李%"); for (User user : userList) { System.out.println(user); } sqlSession.close(); }
-
在sql拼接中使用通配符
<!--存在SQL注入问题--> <select id="getUserLike" resultType="com.cwlin.pojo.User"> select * from mybatis.user where name like #{value} </select>
-
4、配置解析
4.1、核心配置文件
-
mybatis-config.xml
-
Mybatis的配置文件包含了会深深影响MyBatis行为的设置和属性信息。
configuration(配置) properties(属性) settings(设置) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境配置) environment(环境变量) transactionManager(事务管理器) dataSource(数据源) databaseIdProvider(数据库厂商标识) mappers(映射器)
-
在xml中,所有的标签顺序都被规定:The content of element type “configuration” must match “(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)”.
4.2、环境配置 environments
-
MyBatis 可以配置成适应多种环境
-
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境
-
学会使用配置多套运行环境!
-
MyBatis默认的是:事务管理器:JDBC ,连接池:POOLED
-
事务管理器(transactionManager)
- JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
- MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:
- 如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。
-
数据源(dataSource)
- 有三种内建的数据源类型(也就是 type=”[UNPOOLED|POOLED|JNDI]“):
- UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。
- POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
- JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。
- dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
- 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
- 有三种内建的数据源类型(也就是 type=”[UNPOOLED|POOLED|JNDI]“):
4.3、属性 properties
-
我们可以通过properties属性来实现引用配置文件
-
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。【db.poperties】
-
实现步骤:
-
编写一个配置文件 db.properties
driver=com.mysql.cj.jdbc.Driver# 在mybatis-config.xml文件中要使用 & # 在db.properties文件中使用 & 即可url=jdbc:mysql://localhost:3306/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTCusername=rootpassword=123456
-
在核心配置文件中引入
- 可以直接引入外部文件,同时也可以在其中增加一些属性配置
- 如果两个文件有同一个字段,优先使用外部配置文件的字段
<!--引用外部配置文件,注意标签顺序--><properties resource="db.properties"> <property name="username" value="root"/> <property name="password" value="111111"/></properties>
-
4.4、类型别名 typeAliases
-
类型别名可为 Java 类型设置一个缩写名字,它仅用于 XML 配置。意在降低冗余的全限定类名书写。
<!--可以给实体类取别名--><typeAliases> <typeAlias type="com.cwlin.pojo.User" alias="User"/></typeAliases>
-
也可以指定一个包,每一个在包
domain.blog
中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如domain.blog.Author
的别名为author
,;若有注解,则别名为其注解值。见下面的例子:<typeAliases> <package name="com.cwlin.pojo"/></typeAliases>
-
在实体类比较少的时候,使用第一种方式。如果实体类十分多,建议用第二种扫描包的方式。
-
第一种可以DIY别名,第二种不行,如果非要改,需要在实体上增加注解。
@Alias("helloUser")public class User { ...}
-
下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。
别名 映射的类型 _byte byte _long long _short short _int int _integer int _double double _float float _boolean boolean string String byte Byte long Long short Short int Integer integer Integer double Double float Float boolean Boolean date Date decimal BigDecimal bigdecimal BigDecimal object Object map Map hashmap HashMap list List arraylist ArrayList collection Collection iterator Iterator
4.5、设置 Settings
- 这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。下表描述了设置中几个重要设置的含义、默认值等。
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | true | false | false |
useColumnLabel | 使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。 | true | false | true |
useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。 | true | false | False |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true | false | False |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
4.6、其他配置
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins 插件
- mybatis-generator-core
- mybatis-plus
- 通用mapper
4.7、映射器 mappers
-
MapperRegistry:注册绑定我们的Mapper文件
-
在定义 SQL 映射语句之前,我们需要告诉 MyBatis 到哪里去找到这些语句。在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。你可以使用相对于类路径的资源引用,
或完全限定资源定位符(包括,或类名和包名等。file:///
形式的 URL) -
方式一:【推荐使用】
<!-- 使用相对于类路径的资源引用 --><mappers> <mapper resource="com/cwlin/dao/UserMapper.xml"/></mappers>
- 方式二:使用class文件绑定注册
<!-- 使用映射器接口实现类的完全限定类名 --><mappers> <mapper class="com.cwlin.dao.UserMapper"/></mappers>
- 方式三:使用包扫描进行注入
<!-- 将包内的映射器接口实现全部注册为映射器 --><mappers> <package name="com.cwlin.dao"/></mappers>
- 方式二和方式三的注意点:
- 接口和它的Mapper配置文件必须同名
- 接口和它的Mapper配置文件必须在同一个包下
4.8、作用域和生命周期
- 生命周期和作用域是至关重要的,因为错误的使用会导致非常严重的并发问题。
-
SqlSessionFactoryBuilder:
- 一旦创建了SqlSessionFactory,就不再需要它了。
- SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
-
SqlSessionFactory:
- 说白了就可以想象为:数据库连接池
- SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建一个实例。
- SqlSessionFactory的最佳作用域是应用作用域(ApplocationContext)。
- 最简单的就是使用单例模式或静态单例模式。
-
SqlSession:
- 连接到连接池的一个请求。
- SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
- 用完之后需要赶紧关闭,否则资源被占用!
5、解决属性名和字段名不一致的问题
5.1、问题
- 数据库中的字段
- 新建一个项目,拷贝之前的,测试实体类字段不一致的情况
- 测试出现问题
// select * from mybatis.user where id = #{id}// 类型处理器// select id,name,pwd from mybatis.user where id = #{id}
- 解决方法:起别名
<select id="getUserById" resultType="com.cwlin.pojo.User"> select id,name,pwd as password from mybatis.user where id = #{id}</select>
5.2、resultMap
- 结果集映射:id name pwd —> id name password
<!--结果集映射--><resultMap id="UserMap" type="User"> <!--column数据库中的字段,property实体类中的属性--> <!--<result column="id" property="id"/>--> <!--<result column="name" property="name"/>--> <result column="pwd" property="password"/></resultMap><select id="getUserById" resultMap="UserMap"> select * from mybatis.user where id = #{id}</select>
-
resultMap
元素是 MyBatis 中最重要最强大的元素。 -
ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
-
ResultMap
的优秀之处——你完全可以不用显式地配置它们。 -
如果这个世界总是这么简单就好了。
6、日志
6.1、日志工厂
- 如果一个数据库操作,出现了异常,我们需要排错,日志就是最好的助手!
- 曾经:sout、debug
- 现在:日志工厂
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
-
在MyBatis中具体使用哪一个日志实现,在设置中设定
-
STDOUT_LOGGING
<settings> <setting name="logImpl" value="STDOUT_LOGGING"/></settings>
- LOG4J(在下一节中介绍)
-
6.2、Log4j
-
什么是 Log4j?
-
实现步骤:
-
在pom.xml(MyBatis-04)中导入log4j的包
<dependencies> <!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency></dependencies>
-
log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码log4j.rootLogger=DEBUG,console,file#控制台输出的相关设置log4j.appender.console=org.apache.log4j.ConsoleAppenderlog4j.appender.console.Target=System.outlog4j.appender.console.Threshold=DEBUGlog4j.appender.console.layout=org.apache.log4j.PatternLayoutlog4j.appender.console.layout.ConversionPattern=[%c]-%m%n#文件输出的相关设置log4j.appender.file=org.apache.log4j.RollingFileAppenderlog4j.appender.file.File=./log/cwlin.loglog4j.appender.file.MaxFileSize=10mblog4j.appender.file.Threshold=DEBUGlog4j.appender.file.layout=org.apache.log4j.PatternLayoutlog4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n#日志输出级别log4j.logger.org.mybatis=DEBUGlog4j.logger.java.sql=DEBUGlog4j.logger.java.sql.Statement=DEBUGlog4j.logger.java.sql.ResultSet=DEBUGlog4j.logger.java.sq1.PreparedStatement=DEBUG
-
配置settings为log4j实现
<settings> <setting name="logImpl" value="LOG4J"/></settings>
-
测试运行
-
-
Log4j简单使用
-
在要使用Log4j的类中,导入包 import org.apache.log4j.Logger;
-
日志对象,参数为当前类的class对象
static Logger logger = Logger.getLogger(UserMapperTest.class);
-
日志级别:info、debug、error
@Testpublic void testLog4j(){ logger.info("info: 测试log4j"); logger.debug("debug: 测试log4j"); logger.error("error: 测试log4j");}
-
7、分页
- 思考:为什么分页?--------减少数据的处理量
- 在学习mybatis等持久层框架的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,如果查询大量数据的时候,我们往往使用分页进行查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。
7.1、使用Limit分页
- 使用SQL实现分页
-- 语法:select * from user limit startIndex,pageSizeselect * from user limit 3; -- [0,n]
- 使用MyBatis实现分页,核心SQL
-
接口
//分页List<User> getUserByLimit(Map<String,Integer> map);
-
Mapper.xml
<!--分页查询--><select id="getUserByLimit" parameterType="map" resultMap="UserMap"> select * from mybatis.user limit #{startIndex},#{pageSize}</select>
-
在测试类中传入参数测试:起始位置 = (当前页面 - 1) * 页面大小
@Testpublic void getUserByLimit(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); int currentPage = 1; //当前页面 int pageSize = 2; //页面大小 Map<String,Integer> map = new HashMap<>(); map.put("startIndex",(currentPage - 1) * pageSize); map.put("pageSize",pageSize); List<User> userList = mapper.getUserByLimit(map); for (User user : userList) { System.out.println(user); } sqlSession.close();}
7.2、RowBounds分页
- 不再使用SQL实现分页
-
接口
//RowBounds实现分页List<User> getUserByRowBounds();
-
mapper.xml
<!--RowBounds实现分页查询--><select id="getUserByRowBounds" resultMap="UserMap"> select * from mybatis.user</select>
-
在测试类中使用RowBounds类进行测试
@Testpublic void getUserByRowBounds(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); //RowBounds实现 RowBounds rowBounds = new RowBounds(1, 2); //通过Java代码层面实现分页 List<User> userList = sqlSession.selectList("com.cwlin.dao.UserMapper.getUserByRowBounds",null,rowBounds); for (User user : userList) { System.out.println(user); } sqlSession.close();}
7.3、分页插件
8、使用注解开发
8.1、面向接口开发
-
面向接口编程的思想
-
大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程
-
根本原因 : 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准 , 使得开发变得容易 , 规范性更好
-
在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;
-
而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。
-
-
关于接口的理解
-
接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。
-
接口的本身反映了系统设计人员对系统的抽象理解。
-
接口应有两类:
-
第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
-
第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);
-
-
一个体有可能有多个抽象面。抽象体与抽象面是有区别的。
-
-
三个面向区别
- 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性和方法;
- 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现;
- 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体现就是对系统整体的架构;
8.2、使用注解开发
- 实现步骤:
-
注解在接口上实现
public interface UserMapper { @Select("select * from user") List<User> getUsers();}
-
需要在核心配置文件中绑定接口
<!--绑定接口--><mappers> <mapper class="com.cwlin.dao.UserMapper"/></mappers>
-
测试
public class UserMapperTest { @Test public void test(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); //底层主要应用反射机制 UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.getUsers(); for (User user : users) { System.out.println(user); } sqlSession.close(); }}
-
本质:反射机制实现
-
底层:动态代理
8.3、MyBatis详细执行流程
8.4、注解CURD
-
实现步骤:
-
修改MybatisUtils工具类的getSqlSession( ) 方法,重载实现事务自动提交。
public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(true); //设置事务自动提交}//重载public static SqlSession getSqlSession(boolean flag){ return sqlSessionFactory.openSession(flag);}
-
编写UserMapper接口,增加注解
public interface UserMapper { @Select("select * from user") List<User> getUsers(); //方法存在多个参数时,所有的参数前面必须加上@Param("id")注解 @Select("select * from user where id = #{id}") User getUserById(@Param("id") int idx); @Insert("insert into user(id,name,pwd) values(#{id},#{name},#{pwd})") void insertUser(User user); @Update("update user set name=#{name},pwd=#{pwd} where id=#{id}") void updateUser(User user); @Delete("delete from user where id=#{uid}") void deleteUser(@Param("uid") int id);}
-
测试
public class UserMapperTest { @Test public void test(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); //底层主要应用反射机制 UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.getUsers(); for (User user : users) { System.out.println(user); } sqlSession.close(); } @Test public void getUserById(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); //底层主要应用反射机制 UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.getUserById(2); System.out.println(user); sqlSession.close(); } @Test public void insertUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); //底层主要应用反射机制 UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.insertUser(new User(6,"hello","123123")); sqlSession.close(); } @Test public void updateUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); //底层主要应用反射机制 UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.updateUser(new User(6,"hhh","135246")); sqlSession.close(); } @Test public void deleteUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); //底层主要应用反射机制 UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.deleteUser(6); sqlSession.close(); }}
-
-
关于@Param( )注解
-
@Param注解用于给方法参数起一个名字。以下是总结的使用原则:
- 在方法只接受一个参数的情况下,可以不使用@Param。
- 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
- 如果参数是 JavaBean(暂时可以理解为是实体类), 则不能使用@Param。
- 不使用@Param注解时,参数只能有一个,并且是Javabean。
-
简单来说,如下:
- 基本类型的参数或者String类型,需要加上;引用类型不需要加。
- 如果只有一个基本类型的话,可以忽略,但是建议大家都加上。
- 我们在SQL中引用的就是我们这里的@Param()中设定的属性名。
-
-
#{} 和 ${}
-
#{} 的作用主要是替换预编译语句(PrepareStatement)中的?占位符,能够有效防止SQL注入【推荐使用】
INSERT INTO user (name) VALUES (#{name});INSERT INTO user (name) VALUES (?);
-
${} 的作用是直接进行字符串替换,无法防止SQL注入
INSERT INTO user (name) VALUES ('${name}');INSERT INTO user (name) VALUES ('kuangshen');
-
-
使用注解和配置文件协同开发,才是MyBatis的最佳实践!
9、Lombok
-
Lombok项目是一个Java库,它会自动插入编辑器和构建工具中,Lombok提供了一组有用的注释,用来消除Java类中的大量样板代码。仅五个字符(@Data)就可以替换数百行代码从而产生干净,简洁且易于维护的Java类。
-
使用步骤:
-
在IDEA中安装Lombok插件
-
在项目中导入lombok的jar包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --><dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.18</version> <!--<scope>provided</scope>--></dependency>
-
在程序上加注解
@Getter and @Setter@FieldNameConstants@ToString@EqualsAndHashCode@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog@Data@Builder@SuperBuilder@Singular@Delegate@Value@Accessors@Wither@With@SneakyThrows@val
-
-
使用说明:
//实体类@Data //无参构造、get、set、toString、hashCode、equals@AllArgsConstructor //有参构造@NoArgsConstructor //无参构造public class User { private int id; private String name; private String pwd;}
-
缺点:使用Lombok,会增加团队的技术债务,降低代码的可读性,增大代码的耦合度和调式难度
10、多对一处理和一对多处理
10.1、学生和老师的关系
-
多个学生对应一个老师,一个老师对应多个学生
- 对于学生而言,关联,多个学生关联一个老师【多对一】
- 对于老师而言,集合,一个老师拥有多个学生【一对多】
-
数据库设计
CREATE TABLE `teacher` ( `id` INT(10) NOT NULL, `name` VARCHAR(30) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=INNODB DEFAULT CHARSET=utf8INSERT 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-- alter table student ADD CONSTRAINT fk_tid foreign key (tid) references teacher(id) 物理外键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');
10.2、多对一处理
10.2.1、环境搭建
-
导入lombok(见第9节)
-
新建实体类Teacher、Student
@Datapublic class Student { private int id; private String name; private Teacher teacher; //学生需要关联一个老师}
@Datapublic class Teacher { private int id; private String name;}
-
建立Mapper接口
public interface StudentMapper {}
public interface TeacherMapper { @Select("select * from teacher where id = #{tid}" ) Teacher getTeacher(@Param("tid") int id);}
-
建立Mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.cwlin.dao.StudentMapper"> </mapper>
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.cwlin.dao.TeacherMapper"></mapper>
-
在核心配置文件中绑定注册我们的Mapper接口或者文件 【有多种方式】
<?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核心配置文件--><configuration> <!--引用外部配置文件--> <properties resource="db.properties"/> <settings> <!--标准的日志工厂实现--> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <!--可以给实体类取别名--> <typeAliases> <package name="com.cwlin.pojo"/> </typeAliases> <environments default="test"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> <environment id="test"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <!--绑定接口--> <mappers> <mapper class="com.cwlin.dao.StudentMapper"/> <mapper class="com.cwlin.dao.TeacherMapper"/> </mappers></configuration>
-
测试查询是否能够成功
public class MyTest { public static void main(String[] args) { SqlSession sqlSession = MybatisUtils.getSqlSession(); TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); Teacher teacher = mapper.getTeacher(1); System.out.println(teacher); sqlSession.close(); }}
10.2.2、按照查询嵌套处理
- 给StudentMapper接口增加方法
public interface StudentMapper { //查询所有学生的信息,以及对应老师的信息 public List<Student> getStudent();}
- 编写对应的Mapper文件
<!--思路: 1. 查询所有的学生信息 2. 根据查询出来的学生的tid寻找特定的老师--><!--按照查询嵌套处理(子查询)--><select id="getStudent" resultMap="StudentTeacher"> select * from student;</select><resultMap id="StudentTeacher" type="Student"> <result property="id" column="id"/> <result property="name" column="name"/> <!--复杂的属性需要单独处理 对象:association,集合:collection--> <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/></resultMap><select id="getTeacher" resultType="Teacher"> select * from teacher where id = #{tid};</select>
-
编写完毕后,在Mybatis配置文件中,注册Mapper!
-
注意点说明:
<resultMap id="StudentTeacher" type="Student"> <!--association关联属性 property属性名 javaType属性类型 column在多的一方的表中的列名--> <association property="teacher" column="{id=tid,name=tname}" javaType="Teacher" select="getTeacher"/></resultMap><!-- 这里传递过来的id,只有一个属性的时候,下面可以写任何值 association中column多参数配置: column="{key=value,key=value}" 其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的字段名。--><select id="getTeacher" resultType="teacher"> select * from teacher where id = #{id} and name = #{name};</select>
- 测试
@Testpublic void getStudent() { SqlSession sqlSession = MybatisUtils.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); List<Student> students = mapper.getStudent(); for (Student student : students) { System.out.println(student); } sqlSession.close();}
10.2.3、按照结果嵌套处理【推荐】
- 给StudentMapper接口增加方法
public interface StudentMapper { //查询所有学生的信息,以及对应老师的信息 public List<Student> getStudent2();}
- 编写对应的Mapper文件
<!--思路: 1. 查询所有的学生信息 2. 根据查询出来的学生的tid寻找特定的老师--><!--按照结果嵌套处理(联表查询)--><select id="getStudent2" resultMap="StudentTeacher2"> select s.id sid, s.name sname, t.id tid, t.name tname from student s, teacher t where s.tid = t.id;</select><resultMap id="StudentTeacher2" type="Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <!--复杂的属性需要单独处理 对象:association,集合:collection--> <association property="teacher" javaType="Teacher"> <result property="id" column="tid"/> <result property="name" column="tname"/> </association></resultMap>
- 编写完毕后,在Mybatis配置文件中,注册Mapper!(在10.2节中已经处理)
- 测试
@Testpublic void getStudent2() { SqlSession sqlSession = MybatisUtils.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); List<Student> students = mapper.getStudent2(); for (Student student : students) { System.out.println(student); } sqlSession.close();}
10.3、一对多处理
10.3.1、环境搭建
- 实体类
@Datapublic class Student { private int id; private String name; private int tid;}
@Datapublic class Teacher { private int id; private String name; private List<Student> students; //一个老师拥有多个学生}
10.3.2、按照结果嵌套处理【推荐】/按照查询嵌套处理
- TeacherMapper接口编写方法
public interface TeacherMapper { //获取所有老师 List<Teacher> getTeachers(); //获取指定老师下所有学生以及老师的信息 Teacher getTeacher(@Param("tid") int id); Teacher getTeacher2(@Param("tid") int id);}
- 编写接口对应的Mapper配置文件
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.cwlin.dao.TeacherMapper"> <select id="getTeachers" resultType="Teacher"> select * from teacher </select> <!--按照结果嵌套查询(子查询)--> <select id="getTeacher" resultMap="TeacherStudent"> select s.id sid, s.name sname, t.id tid, t.name tname from student s, teacher t where s.tid = t.id and tid = #{tid}; </select> <resultMap id="TeacherStudent" type="Teacher"> <result property="id" column="tid"/> <result property="name" column="tname"/> <!--复杂的属性需要单独处理 对象:association,集合:collection javaType="" 指定属性的类型! 集合中的泛型信息,我们使用 ofType 获取 --> <collection property="students" ofType="Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <result property="tid" column="tid"/> </collection> </resultMap> <!--按照查询嵌套处理(联表查询)--> <select id="getTeacher2" resultMap="TeacherStudent2"> select * from teacher where id = #{tid}; -- 老师的id总是为0 </select> <resultMap id="TeacherStudent2" type="Teacher"> <collection property="students" column="id" javaType="ArrayList" ofType="Student" select="getStudent"/> </resultMap> <select id="getStudent" resultType="Student"> select * from student where tid = #{id}; </select></mapper>
-
将Mapper文件注册到MyBatis-config文件中
-
测试
public class MyTest { public static void main(String[] args) { SqlSession sqlSession = MybatisUtils.getSqlSession(); TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); List<Teacher> teachers = mapper.getTeachers(); for (Teacher teacher : teachers) { System.out.println(teacher); } sqlSession.close(); } @Test public void getTeacher(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); Teacher teacher = mapper.getTeacher(1); System.out.println(teacher); sqlSession.close(); } @Test public void getTeacher2(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); Teacher teacher = mapper.getTeacher2(1); System.out.println(teacher); sqlSession.close(); }}
10.4、小结
-
association & collection
- 关联 - association 【多对一】【一对一】
- 集合 - collection 【一对多】
- javaType & ofType 都是用来指定对象类型的
- JavaType 用来指定pojo中实体类的类型
- ofType 用来指定映射到List或者集合中的pojo类型,泛型中的约束类型
-
注意点:
- 保证SQL的可读性,尽量保证通俗易懂
- 根据实际要求,尽量编写性能更高的SQL语句
- 注意一对多和多对一,属性名和字段不一致的问题
- 尽量使用Log4j,通过日志来查看自己的错误
-
面试高频
- Mysql引擎
- InnoDB底层原理
- 索引
- 索引优化
11、动态SQL
-
什么是动态SQL:动态SQL就是根据不同的条件生成不同的SQL语句。
-
所谓的动态SQL,本质上还是SQL语句,只是我们可以在SQL层面,去执行一个逻辑代码。
-
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
-
if, choose, when, otherwise, trim, where, set, foreach
11.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
-
创建一个基础工程
-
编写配置文件
<settings> <!--标准的日志工厂实现--> <setting name="logImpl" value="STDOUT_LOGGING"/> <!--开启驼峰命名规则自动映射--> <setting name="mapUnderscoreToCamelCase" value="true"/></settings>
-
编写实体类
import java.util.Date;@Datapublic class Blog { private String id; private String title; private String author; private Date createTime; private int views;}
-
编写实体类对应Mapper接口和Mapper.xml文件
public interface BlogMapper {}
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.cwlin.dao.BlogMapper"></mapper>
-
IdUtil工具类
public class IdUtils { public static String getId(){ return UUID.randomUUID().toString().replaceAll("-",""); }}
-
-
插入初始数据
-
编写实体类对应Mapper接口和Mapper.xml文件
public interface BlogMapper { //插入数据 void insertBlog(Blog blog);}
<mapper namespace="com.cwlin.dao.BlogMapper"> <select id="insertBlog" parameterType="blog"> insert into mybatis.blog (id, title, author, create_time, views) values (#{id}, #{title}, #{author}, #{createTime}, #{views}); </select></mapper>
-
测试插入初始数据
public class MyTest { @Test public void insertBlog(){ SqlSession sqlSession = MybatisUtils.getSqlSession(true); //自动提交事务 BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); Blog blog = new Blog(); blog.setAuthor("cwlin"); blog.setCreateTime(new Date()); blog.setViews(9999); blog.setId(IdUtils.getId()); blog.setTitle("Mybatis如此简单"); mapper.insertBlog(blog); blog.setId(IdUtils.getId()); blog.setTitle("Java如此简单"); mapper.insertBlog(blog); blog.setId(IdUtils.getId()); blog.setTitle("Spring如此简单"); mapper.insertBlog(blog); blog.setId(IdUtils.getId()); blog.setTitle("微服务如此简单"); blog.setViews(1000); mapper.insertBlog(blog); sqlSession.close(); }}
-
11.2、动态SQL标签
11.2.1、if
-
编写实体类对应Mapper接口和Mapper.xml文件
//查询博客List<Blog> selectBlogIf(Map<String,String> map);
<select id="selectBlogIf" parameterType="map" resultType="blog"> select * from mybatis.blog where true <!--可以用where标签--> <if test="title != null"> and title = #{title} </if> <if test="author != null"> and author = #{author} </if></select>
-
测试
@Testpublic void selectBlogIf(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap<String,String> map = new HashMap<>(); //map.put("title","Java如此简单"); map.put("author","cwlin"); List<Blog> blogs = mapper.selectBlogIf(map); for (Blog blog : blogs) { System.out.println(blog); } sqlSession.close();}
11.2.2、choose (when, otherwise)
-
编写实体类对应Mapper接口和Mapper.xml文件
List<Blog> selectBlogWhen(Map<String,String> map);
<select id="selectBlogWhen" parameterType="map" resultType="blog"> select * from mybatis.blog <where> <choose> <when test="title != null"> and title = #{title} </when> <when test="author != null"> and author = #{author} </when> <otherwise> and views = #{views} </otherwise> </choose> </where></select>
-
测试
@Testpublic void selectBlogWhen(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap<String,String> map = new HashMap<>(); //map.put("title","微服务如此简单"); map.put("author","cwlin"); map.put("views","9999"); List<Blog> blogs = mapper.selectBlogWhen(map); for (Blog blog : blogs) { System.out.println(blog); } sqlSession.close();}
11.2.3、trim (where、set)
-
where、set
- where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
- set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
<select id="selectBlogIf" parameterType="map" resultType="blog"> select * from mybatis.blog <where> <if test="title != null"> and title = #{title} </if> <if test="author != null"> and author = #{author} </if> </where></select><update id="updateBlog" parameterType="map"> update mybatis.blog <set> <if test="title != null"> title = #{title}, </if> <if test="author != null"> author = #{author}, </if> </set> where id = #{id}</update>
-
trim
- prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的后缀内容,并且插入 prefix 属性中指定的后缀内容。
- suffixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 suffixOverrides 属性中指定的前缀内容,并且插入 suffix 属性中指定的前缀内容。
<trim prefix="WHERE" prefixOverrides="AND |OR "> ...</trim><trim prefix="SET" suffixOverrides=","> ...</trim>
11.2.4、foreach
-
官方文档
-
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:
<select id="selectPostIn" resultType="domain.blog.Post"> SELECT * FROM POST P WHERE ID in <foreach item="item" index="index" collection="list" open="(" separator="," close=")"> #{item} </foreach></select>
-
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!
-
提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
-
-
具体实现
-
编写实体类对应Mapper接口和Mapper.xml文件
//查询第1,2,3号记录的博客List<Blog> selectBlogForeach(Map<String, ArrayList<Integer>> map);
<select id="selectBlogForeach" parameterType="map" resultType="blog"> select * from mybatis.blog <where> <!--select * from blog where 1=1 and (id=1 or id=2 or id=3) collection:指定输入对象中的集合属性 item:每次遍历生成的对象 open:开始遍历时的拼接字符串 close:结束时拼接的字符串 separator:遍历对象之间需要拼接的字符串 --> <foreach collection="ids" item="id" open="and (" separator="or" close=")"> id = #{id} </foreach> </where></select>
-
测试
@Testpublic void selectBlogForeach(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap<String,ArrayList<Integer>> map = new HashMap<>(); ArrayList<Integer> ids = new ArrayList<>(); ids.add(1); ids.add(2); ids.add(3); map.put("ids",ids); List<Blog> blogs = mapper.selectBlogForeach(map); for (Blog blog : blogs) { System.out.println(blog); } sqlSession.close();}
11.3、SQL片段
-
有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。
-
提取SQL片段:
<sql id="if-title-author"> <if test="title != null"> and title = #{title} </if> <if test="author != null"> and author = #{author} </if></sql>
-
引用SQL片段:
<select id="selectBlogIf" parameterType="map" resultType="blog"> select * from mybatis.blog <where> <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace --> <include refid="if-title-author"/> <!-- 在这里还可以引用其他的 sql 片段 --> </where></select>
-
注意:
- 最好基于单表来定义 sql 片段,提高片段的可重用性
- 在 sql 片段中不要包括 where 标签,因为会自动去除一些东西
11.4、小结
- 动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合就可以了。
- 其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改,防止出错。
- 建议:先在Mysql中写出完整的SQL,再对应的去修改成我们的动态SQL实现通用即可。
- 多在实践中使用才是熟练掌握它的技巧。动态SQL在开发中大量的使用,一定要熟练掌握!
12、缓存
12.1、简介
-
什么是缓存[Cache]?
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
-
为什么使用缓存?
-
在实际应用中,一般是读的次数大于写
-
减少和数据库的交互次数,减少系统开销,提高系统效率
-
-
什么样的数据可以使用缓存?
- 经常查询并且不经常改变的数据 【可以使用缓存】
- 不经常查询并且经常改变的数据 【不要使用缓存】
12.2、MyBatis缓存
- MyBatis包含一个非常强大的查询缓存特性,它可以非常方便的定制和配置缓存,缓存可以极大的提高查询效率。
- MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启(SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高可扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来定义二级缓存。
12.3、一级缓存
-
一级缓存也叫本地缓存:SqlSession
- 与数据库同一次会话期间查询到的数据会放在本地缓存中
- 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库
-
测试步骤:
-
开启日志
-
测试在一个Session中查询两次记录
@Testpublic void selectUserById(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.selectUserById(1); System.out.println(user); System.out.println("============================"); User user2 = mapper.selectUserById(1); System.out.println(user2); System.out.println(user==user2); sqlSession.close();}
-
查看日志输出(每执行一次SQL查询,就会进行预编译,除非是直接读取缓存)
-
-
一级缓存失效
- 一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;
- 一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!
-
一级缓存失效的情况:
- sqlSession不同
@Testpublic void selectUserById(){ SqlSession session = MybatisUtils.getSession(); SqlSession session2 = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class); UserMapper mapper2 = session2.getMapper(UserMapper.class); User user = mapper.selectUserById(1); System.out.println(user); System.out.println("============================"); User user2 = mapper2.selectUserById(1); System.out.println(user2); System.out.println(user==user2); session.close(); session2.close();}
-
观察结果:发现发送了两条SQL语句!
-
结论:每个sqlSession中的缓存相互独立
- sqlSession相同,查询的Mapper.xml不同
//略
- sqlSession相同,查询条件不同
@Testpublic void selectUserById(){ SqlSession session = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class); UserMapper mapper2 = session.getMapper(UserMapper.class); User user = mapper.selectUserById(1); System.out.println(user); System.out.println("============================"); User user2 = mapper2.selectUserById(2); System.out.println(user2); System.out.println(user==user2); session.close();}
-
观察结果:发现发送了两条SQL语句!很正常的理解
-
结论:当前缓存中,不存在这个数据
- sqlSession相同,两次查询之间执行了增删改操作!
@Testpublic void selectUserById(){ SqlSession session = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class); User user = mapper.selectUserById(1); System.out.println(user); HashMap map = new HashMap(); map.put("name","cwlin"); map.put("id",6); mapper.updateUser(map); System.out.println("============================"); User user2 = mapper.selectUserById(1); System.out.println(user2); System.out.println(user==user2); session.close();}
-
观察结果:查询在中间执行了增删改操作后,重新执行了
-
结论:因为增删改操作可能会(即使没有)对当前数据产生影响,所以必定会刷新缓存
- sqlSession相同,手动清除一级缓存
@Testpublic void selectUserById(){ SqlSession session = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class); User user = mapper.selectUserById(1); System.out.println(user); session.clearCache(); //手动清除缓存 User user2 = mapper.queryUserById(1); System.out.println(user2); System.out.println(user==user2); session.close();}
12.4、二级缓存
-
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
-
基于namespace级别的缓存,一个名称空间,对应一个二级缓存
-
工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
- 新的会话查询信息,就可以从二级缓存中获取内容
- 不同的mapper查询出的数据会放在自己对应的缓存(map)中
-
测试步骤:
-
开启全局缓存
<!--显式地开启全局缓存--><setting name="cacheEnabled" value="true"/>
-
在Mapper.xml中使用缓存(可以选择是否自定义参数)
<!--在当前Mapper.xml中使用二级缓存--><!--这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新, 最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的, 因此对它们进行修改可能会在不同线程中的调用者产生冲突。--><cache/><cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
-
代码测试
@Testpublic void selectUserById(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); SqlSession sqlSession2 = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.selectUserById(1); System.out.println(user); sqlSession.close(); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); User user2 = mapper2.selectUserById(1); System.out.println(user2); System.out.println(user==user2); sqlSession2.close();}
-
问题:我们需要将实体类序列化,否则就会报错!当readOnly="false"时,就需要将实体类序列化。
org.apache.ibatis.cache.CacheException: Error serializing object.
@Data@AllArgsConstructor@NoArgsConstructorpublic class User implements Serializable { private int id; private String name; private String pwd;}
-
-
小结:
- 只要开启了二级缓存,我们在同一个Mapper中的查询,就可以在二级缓存中拿到数据
- 查询到的数据都会被默认先放在一级缓存中
- 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中
12.5、缓存原理
-
注意:只有查询才有缓存,根据数据是否需要缓存(根据修改是否频繁选择是否开启)useCache=“true”
<select id="getUserById" resultType="user" useCache="true"> select * from user where id = #{id}</select>
12.6、自定义缓存 - EhCache
-
Ehcache(第三方缓存实现)是一种广泛使用的开源Java分布式缓存,主要面向通用缓存。
-
使用步骤:
-
要在应用程序中使用Ehcache,需要引入依赖的jar包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --><dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.1</version></dependency>
-
在mapper中指定使用我们的ehcache缓存实现
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
-
编写ehcache.xml文件,如果在加载时未找到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>
-
-
合理的使用缓存,可以让我们程序的性能大大提升!