Mybatis
1、简介
1.1、什么是Mybatis
- MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
- MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
- MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型。
- MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。
- maven依赖:
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
-
**Maven仓库地址:**https://mvnrepository.com/
-
**中文文档:**https://mybatis.net.cn/index.html
1.2、为什么要用Mybatis?
- 帮助程序员将数据存到数据库里
- 方便
- 传统的JDBC太复杂了
- 优点:
- 简单易学
- 灵活
- sql和代码分离,提高了可维护性
- 提供映射标签,支持对象与数据库的ORM字段关系映射
- 提供对象关系映射标签,支持对象关系组建维护
- 提供xml标签,支持编写动态sql
最重要的:使用的人多
2、第一个Mybatis程序
搭建环境–>导入Mybatis–>编写代码–>测试
2.1、搭建环境
创建数据库
create database `mybatis`
use `mybatis`
create table `user`(
`id` int(20) primary key,
`name` varchar(200) default null,
`pwd` varchar(200) default null
)engine=innodb default charset utf8;
insert into `user` values
(1,'mybatis1号','asdf'),
(2,'mybatis2号','asdfasdf'),
(3,'mybatis3号','123456')
创建项目
-
Idea新建空的maven项目
-
删除src目录(将项目变成父工程)
-
pom导入依赖
<dependencies> <!--mysql依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <!--mybatis依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <!--juint依赖--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> </dependency> </dependencies>
2.2、创建子模块
-
父项目 New Module (新建子项目)
-
子项目resource目录下新建文件
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"> <!--mybatis核心配置文件--> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/smbms?useUnicode=true&characterEncoding=utf8&useSSL=true"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> </configuration>
2.3、编写代码
-
编写MybatisUtils工具类
public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { //第一步:获取sqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { throw new RuntimeException(e); } } //获取sqlSession,可以操作数据库的对象 public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
-
实体类
public class User implements Serializable { private int id; private String name; private String pwd; //省略构造方法、get、set方法 }
-
Mapper接口
public interface UserMapper { List<User> getAllUsers(); }
-
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"> <!--mybatis-3-mapper.dtd:约束文件的名称,限制和检查在当前文件中出现的标签和属性符合mybatis的要求--> <!--namespace:命名空间,要有唯一的值,要求使用dao接口的权限定名称(一个dao接口对应一个mapper,namespace指明对应哪个dao接口)--> <mapper namespace="com.lx.dao.UserMapper"> <!-- 所有的数据库操作都要写在mapper标签中,可以使用特定的标签表示数据库中的特定操作 --> <!-- id:对应的UserMapper中方法名 resultType:对应的方法返回类型 --> <select id="getAllUsers" resultType="com.lx.pojo.User"> select * from `user` </select> </mapper>
-
pom文件需要加入配置(maven导出资源问题,放入pom.xml文件中)
<!--静态资源导出,不管是java文件下还是resources文件下,都导出--> <build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
-
mybatis-config.xml
需要将写好的Mapper注册进去<!--mybatis核心配置文件--> <configuration> <!--注册mapper才可以被调用--> <mappers> <mapper resource="com/lx/daoimpl/UserMapper.xml"/> </mappers> </configuration>
-
测试
@Test public void testGetAllUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); //方式1(推荐使用) UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> allUsers = mapper.getAllUsers(); //方式2(需要强制换,不推荐) // List<User> allUsers = sqlSession.selectList("com.lx.dao.UserMapper.getAllUsers"); for (User user : allUsers) { System.out.println(user); } sqlSession.close(); }
-
输出
User{id=1, name='mybatis1号', pwd='asdf'} User{id=2, name='mybatis2号', pwd='asdfasdf'} User{id=3, name='mybatis3号', pwd='123456'}
3、认识Mapper.xml
中属性
dtd头文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
mapper:所有的数据库操作都要写在mapper标签中,可以使用特定的标签表示数据库中的特定操作
namespace:命名空间,要有唯一的值,值和Dao/Mapper的包名一致
id:对应的UserMapper中方法名
resultType:对应的方法返回类型
parameterType:参数值类型(int、String、实体类等等)
select:查询标签
insert:新增标签
update:更新标签
delete:删除标签
4、增删改查
注意点:增删改需要提交事务才可以操作成功
提交事务两种方法:
1、自动提交
//设置为true,则将自动提交事务打开
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
2、手动提交
@Test
public void addUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.addUser(new User(4,"Mybatis4号","asdfasfadsfasdfadsf"));
//手动通过commit方法提交事务
sqlSession.commit();
sqlSession.close();
}
-
查
//根据Id获取用户 User getUserById(int id); <select id="getUserById" resultType="com.lx.pojo.User" parameterType="int"> select * from `user` where id = #{id} </select> @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(); } 输出:User{id=1, name='mybatis1号', pwd='asdf'}
-
增**(提交事务)**
//新增用户 void addUser(User user); <insert id="addUser" parameterType="com.lx.pojo.User"> insert into `user` values(#{id},#{name},#{pwd}) </insert> @Test public void addUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.addUser(new User(4,"Mybatis4号","asdfasfadsfasdfadsf")); //需要提交事务 sqlSession.commit(); sqlSession.close(); }
-
改**(提交事务)**
//根据Id修改用户 void updateUser(User user); <update id="updateUser" parameterType="com.lx.pojo.User"> update `user` set name = #{name},pwd = #{pwd} where id = #{id} </update> @Test public void updateUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.updateUser(new User(4,"mmmmmm","123456789")); //需要提交事务 sqlSession.commit(); sqlSession.close(); }
-
删**(提交事务)**
//根据Id删除用户 void deleteUserById(int id); <delete id="deleteUserById" parameterType="int"> delete from `user` where id = #{id} </delete> @Test public void deleteUserById(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.deleteUserById(4); //需要提交事务 sqlSession.commit(); sqlSession.close(); }
5、万能的Map
当参数过多的时候,我们可以考虑使用Map介入
//将参数设为Map类型
User getUserByMap(Map<String,Object> map);
<!--参数类型用map,下方传入的值是map的key,可以自定义参数名-->
<select id="getUserByMap" resultType="com.lx.pojo.User" parameterType="map">
select * from `user` where id = #{userId} and name = #{userName}
</select>
@Test
public void getUserByMap(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("userId",2);
map.put("userName","mybatis2号");
User user = mapper.getUserByMap(map);
System.out.println(user);
sqlSession.close();
}
6、模糊查询
正常sql:
select * from `user` where `name` like '%号%'
Mybati中使用:
1、调用时拼接 %value%
List<User> getUserByLikeName(String name);
<select id="getUserByLikeName" resultType="com.lx.pojo.User">
select * from `user` where `name` like #{name}
</select>
@Test
public void getUserByLikeName(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> lists = mapper.getUserByLikeName("%号%");
for (User user : lists) {
System.out.println(user);
}
sqlSession.close();
}
2、使用mysql的concat函数拼接
<select id="getUserByLikeName" resultType="com.lx.pojo.User">
select * from `user` where `name` like concat('%',#{name},'%')
</select>
@Test
public void getUserByLikeName(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> lists = mapper.getUserByLikeName("2");
for (User user : lists) {
System.out.println(user);
}
sqlSession.close();
}
3、xml中手动拼接
<select id="getUserByLikeName" resultType="com.lx.pojo.User">
select * from `user` where name like "%"#{name}"%"
</select>
7、配置文件中标签属性解析
7.1、各个标签含义
Mybatis核心配置文件:mybatis-config.xml
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
7.2、环境配置(environments)
MyBatis 可以配置成适应多种环境
default配置默认使用的数据源
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
<environments default="test">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value=""/>
<property name="url" value=""/>
<property name="username" value=""/>
<property name="password" value=""/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value=""/>
<property name="url" value=""/>
<property name="username" value=""/>
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
注意一些关键点:
- 默认使用的环境 ID(比如:default=“development”)。
- 每个 environment 元素定义的环境 ID(比如:id=“development”)。
- 事务管理器的配置(比如:type=“JDBC”)。
- 数据源的配置(比如:type=“POOLED”)。
默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。
事务管理器(transactionManager)
在 MyBatis 中有两种类型的事务管理器(也就是 type=“[JDBC|MANAGED]”):默认是JDBC
-
JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
-
MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。
数据源(dataSource)
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
- 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
有三种内建的数据源类型(也就是 type=“[UNPOOLED|POOLED|JNDI]”):默认是POOLED
-
UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。
-
POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
-
JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。
总结:事务管理器默认是JDBC,数据源默认是POOLED
7.3、属性(properties)
这些属性可以在外部进行配置,并可以进行动态替换。
- resource目录下新建
db.properties
文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true
username=root
password=root
mybatis-config.xml
核心配置文件
<configuration>
<!--将配置文件加载进来,供下边可${}调用获取配置文件的值-->
<properties resource="db.properties"></properties>
<environments default="development">
<environment id="development">
<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>
</configuration>
也可以这样写:db.properties文件只写两个属性,剩下的属性在核心配置文件中配置
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true
<configuration>
<properties resource="db.properties">
<property name="name" value="root"/>
<property name="pwd" value="root"/>
</properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${name}"/>
<property name="password" value="${pwd}"/>
</dataSource>
</environment>
</mappers>
</configuration>
总结:
- 可以直接引入外部文件如:
db.properties
- 可以直接在核心配置文件中配置属性
- 如果db.properties和mybatis-config.xml也配置了相同的属性值,会优先读取db.properties中的配置
7.4、类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
三种方法定义别名:
1:核心文件中通过进行配置
<typeAliases>
<typeAlias type="com.lx.pojo.User" alias="bieming1"></typeAlias>
</typeAliases>
<select id="getAllUsers" resultType="bieming1">
select * from `user`
</select>
2:核心文件中通过进行配置
用package引用实体类包名,会使用实体类的首字母小写
类名来作为它的别名
<typeAliases>
<package name="com.lx.pojo"/>
</typeAliases>
<select id="getAllUsers" resultType="user">
select * from `user`
</select>
3:实体类使用注解自定义别名
@Alias("hahaha")
public class User implements Serializable {}
<select id="getAllUsers" resultType="hahaha">
select * from `user`
</select>
总结:
- 实体类多可以使用第一种方法
- 实体类较少可以使用第二种方法
- 第一种和第三种可以自定义别名名称
7.5、设置(settings)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。详情看文档链接:https://mybatis.net.cn/configuration.html#settings
7.6、映射器(mappers)
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。
Mapper写完需要做映射,否则会抛异常org.apache.ibatis.binding.BindingException
方式1:resource寻找xml文件路径
<mappers>
<!--使用相对于类路径的资源引用-->
<mapper resource="com/lx/daoimpl/UserMapper2.xml"/>
</mappers>
方式2:class引入
<mappers>
<!--使用映射器接口实现类的完全限定类名-->
<mapper class="com.lx.dao.UserMapper2"/>
</mappers>
class引入注意点:接口类和实现类xml文件必须命名相同,而且要在同一包下
方式3:引入包路径
<mappers>
<package name="com.lx.dao"/>
</mappers>
包路径引入注意点和class引入一样:接口类和实现类xml文件必须命名相同,而且要在同一包下
总结:推荐使用resource寻找xml文件路径来做映射,约束小
8、作用域和生命周期
不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
SqlSessionFactoryBuilder–创造–>SqlSessionFactory–获取–>SqlSession
SqlSessionFactoryBuilder:
- 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。
- SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
- 可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory:
- SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
- 使用 SqlSessionFactory 的最佳实践是在运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。
- 因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession:
- 每个线程都应该有它自己的 SqlSession 实例。
- SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
- 代码最后要执行sqlSession.close();关闭资源是很重要的。
9、结果集映射 (resultMap)
resultMap
元素是 MyBatis 中最重要最强大的元素。
ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
发现问题:之前的实体类POJO中字段都是与数据库一致,不用我们做映射,mybatis会自动映射,但是当两边字段不一致时,字段映射不上,无法赋值如何处理
数据库字段: |
---|
id |
name |
pwd |
实体类字段: |
---|
private int userid; |
private String username; |
private String password; |
-
方式1:xml中用as别名
<select id="getUserById" resultType="user"> select id as userid,name as username,pwd as password from `user` where id = #{id} </select>
-
方式2:使用resultMap结果集映射
<resultMap id="ysUser" type="user"> <!--column:列,是指数据库中字段名, property:属性,指的是实体类中的字段名--> <result column="id" property="userid"></result> <result column="name" property="username"></result> <result column="pwd" property="password"></result> </resultMap> <!--返回类型resultType改为结果集映射resultMap--> <select id="getUserById" resultMap="ysUser"> select * from `user` where id = #{id} </select>
-
如果这个世界总是这么简单就好了。
10、日志
在mybatis-config.xml
核心配置文件中使用setting标签设置
logImpl:指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
-
SLF4J
-
LOG4J(掌握)
-
LOG4J2
-
JDK_LOGGING
-
COMMONS_LOGGING
-
STDOUT_LOGGING(掌握,标准日志输出)
-
NO_LOGGING
10.1、日志工厂(STDOUT_LOGGING)
使用STDOUT_LOGGING配置日志
<!--标准日志工厂输出:STDOUT_LOGGING standard out put-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
配置后,运行的sql会有日志输出,效果如下:
Created connection 111374580.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6a370f4]
==> Preparing: select * from `user` where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, mybatis1号, asdf
<== Total: 1
User{userid=1, username='mybatis1号', password='asdf'}
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6a370f4]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6a370f4]
Returned connection 111374580 to pool.
10.2、LOG4J
什么是LOG4J?
- Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件。
- 我们也可以控制每一条日志的输出格式。
- 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
- 这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
-
导入依赖(此版本有漏洞,后面再用log4j2)
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
-
resource目录下新建
log4j.properties
文件# 设置日志级别和输出目的地 log4j.rootLogger=DEBUG, stdout, file # 控制台输出的相关设置 log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n # 文件输出的相关设置 log4j.appender.file=org.apache.log4j.RollingFileAppender log4j.appender.file.File=logs/app.log log4j.appender.file.MaxFileSize=5MB log4j.appender.file.MaxBackupIndex=10 log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
-
mybatis-config.xml
文件配置<settings> <!--配置log4j日志输出--> <setting name="logImpl" value="LOG4J"/> </settings>
-
执行test类中调用mapper方法输出
2024-01-26 13:46:52 DEBUG PooledDataSource:434 - Created connection 428996455. 2024-01-26 13:46:52 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1991f767] 2024-01-26 13:46:52 DEBUG getUserById:137 - ==> Preparing: select * from `user` where id = ? 2024-01-26 13:46:52 DEBUG getUserById:137 - ==> Parameters: 1(Integer) 2024-01-26 13:46:52 DEBUG getUserById:137 - <== Total: 1 User{userid=1, username='mybatis1号', password='asdf'} 2024-01-26 13:46:52 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1991f767] 2024-01-26 13:46:52 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1991f767] 2024-01-26 13:46:52 DEBUG PooledDataSource:391 - Returned connection 428996455 to pool.
-
运用在代码中
public class UserMapperTest { private static Logger logger = Logger.getLogger(UserMapperTest.class); @Test public void testLog4j(){ logger.info("info:--进入了testLog4j"); logger.debug("debug:--进入了testLog4j"); logger.error("error:--进入了testLog4j"); } }
-
输出
2024-01-26 13:50:08 INFO UserMapperTest:27 - info:--进入了testLog4j 2024-01-26 13:50:08 DEBUG UserMapperTest:28 - debug:--进入了testLog4j 2024-01-26 13:50:08 ERROR UserMapperTest:29 - error:--进入了testLog4j
11、分页
为什么要使用分页?
处理数据量过多查询
原sql查询
select * from user limit startIndex,pageSize; startIndex:起始索引 pageSize:每页条数
select * from user limit 3; 只传一个数字代表查询0,3
11.1、使用sql中limit分页
-
UserMapper接口
public interface UserMapper4 { //分页获取用户 List<User> getUsersByLimit(Map<String,Integer> map); }
-
UserMapper.xml
<mapper namespace="com.lx.dao.UserMapper4"> <resultMap id="yingsheUser" type="user"> <result column="pwd" property="password"></result> </resultMap> <select id="getUsersByLimit" resultMap="yingsheUser" parameterType="map"> select * from `user` limit #{startIndex},#{pageSize} </select> </mapper>
-
Test测试
public class UserMapperTest { private static Logger logger = Logger.getLogger(UserMapperTest.class); @Test public void testLimit(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper4 mapper = sqlSession.getMapper(UserMapper4.class); HashMap<String, Integer> map = new HashMap<>(); map.put("startIndex",1); map.put("pageSize",2); List<User> userList = mapper.getUsersByLimit(map); for (User user : userList) { logger.info(user); } sqlSession.close(); } }
-
输出
2024-01-26 15:15:00 DEBUG getUsersByLimit:137 - ==> Preparing: select * from `user` limit ?,? 2024-01-26 15:15:00 DEBUG getUsersByLimit:137 - ==> Parameters: 1(Integer), 2(Integer) 2024-01-26 15:15:00 DEBUG getUsersByLimit:137 - <== Total: 2 2024-01-26 15:15:00 INFO UserMapperTest:22 - User{id=2, name='mybatis2号', password='asdfasdf'} 2024-01-26 15:15:00 INFO UserMapperTest:22 - User{id=3, name='mybatis3号', password='123456'}
11.2、使用RowBounds的java代码分页
-
UserMapper接口
List<User> getUsersByRowBounds();
-
UserMapper.xml
<select id="getUsersByRowBounds" resultType="user" resultMap="yingsheUser"> select * from `user` </select>
-
Test
@Test public void testRowBounds(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); List<User> userList = sqlSession.selectList("com.lx.dao.UserMapper4.getUsersByRowBounds", null, new RowBounds(1, 2)); for (User user : userList) { logger.info(user); } sqlSession.close(); }
-
输出
2024-01-26 15:27:52 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@44e3760b] 2024-01-26 15:27:52 DEBUG getUsersByRowBounds:137 - ==> Preparing: select * from `user` 2024-01-26 15:27:52 DEBUG getUsersByRowBounds:137 - ==> Parameters: 2024-01-26 15:27:52 INFO UserMapperTest:33 - User{id=2, name='mybatis2号', password='asdfasdf'} 2024-01-26 15:27:52 INFO UserMapperTest:33 - User{id=3, name='mybatis3号', password='123456'}
12、使用注解开发
增删改查
public interface CommentUserMapper {
@Select("select * from `user`")
List<User> getAllUsers();
//sql语句中的参数要和@Param中的值一致
@Select("select * from `user` where id = #{userId}")
User getUserById(@Param("userId") int id);
//当参数为实体类时,sql中赋值参数应当与实体类中的字段一致
@Insert("insert into `user` values (#{id},#{name},#{password})")
int addUser(User user);
@Update("update `user` set `name`=#{name},pwd=#{password} where id = #{id}")
int updateUser(User user);
@Delete("delete from `user` where id = #{id}")
int deleteUser(int id);
}
【这里注意要设置自动提交,删改查数据才会成功执行】
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//第一步:获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//获取sqlSession,可以操作数据库的对象
public static SqlSession getSqlSession(){
//设置为true,自动提交事务
return sqlSessionFactory.openSession(true);
}
}
注意点:
- 简单sql可以通过注解进行开发
- 当多个参数为基本数据类型或者String类型时,需要使用@Param注解声明参数
- 参数只有一个可以省略不写,但是遵循规范,最好都加上
- 引用类型不用带参数(实体类)
- 注解中的sql语句中的参数名
#{val}
引用的是@Param("val")
中的值 - 当参数为实体类时,sql中赋值参数名
#{val}
应当与实体类中的字段一致 - 使用#{},不使用${},预防sql注入
13、Lombok
什么是Lombok?
Lombok项目是一个java库,它可以自动插入到编辑器和构建工具中,增强java的性能。不需要再写getter、setter或equals方法,只要有一个注解,就有一个功能齐全的构建器、自动记录变量等等。
如何使用Lombok?
-
idea导入lombok插件
-
项目导入lombok依赖
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.26</version> </dependency>
-
常用注解
- Data:整合了Getter、Setter、ToString、EqualsAndHashCode、RequiredArgsConstructor注解。
- Getter:快速构建Getter方法。
- Setter:快速构建Setter方法。
- ToString:快速将当前对象转换成字符串类型,便于log。
- EqualsAndHashCode:快速进行相等判断。
- NonNull:判断变量(对象)是否为空
14、复杂查询(多对一)
多个学生对应一个老师
-
数据库创建
-- 四个学生对应一个老师 -- 也可以是一个老师有多个学生 create table student( id int(10) primary key, name varchar(20), tid int(10) ); insert into student values (1,"学生1",1), (2,"学生2",1), (3,"学生3",1), (4,"学生4",1); create table teacher( id int(10) primary key, name varchar(20) ); insert into teacher values (1,"刘老师");
-
实体类创建
@Data public class Teacher { private int id; private String name; }
@Data public class Student { private int id; private String name; private Teacher teacher; }
-
Mapper接口
public interface StudentMapper { List<Student> getAllStudents(); }
-
Mapper.xml文件
- 通过查询嵌套映射,先查询结果,后对结果再嵌套查询,再映射(子查询)
<mapper namespace="com.lx.dao.StudentMapper"> <select id="getAllStudents" resultMap="StudentTeacher"> select * from student </select> <resultMap id="StudentTeacher" type="com.lx.pojo.Student"> <result property="id" column="id"></result> <result property="name" column="name"></result> <!--复杂的属性,需要单独处理,对象时用:association,集合时用:collection--> <association property="teacher" column="tid" javaType="com.lx.pojo.Teacher" select="getAllTeacher"></association> </resultMap> <select id="getAllTeacher" resultType="com.lx.pojo.Teacher"> select * from teacher where id = #{id} </select> </mapper>
- 按照结果嵌套映射,先查询结果,后对字段进行映射(联表查询)
<select id="getAllStudents2" 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="com.lx.pojo.Student"> <result property="id" column="sid"></result> <result property="name" column="sname"></result> <association property="teacher" javaType="com.lx.pojo.Teacher"> <result property="id" column="tid"></result> <result property="name" column="tname"></result> </association> </resultMap>
15、复杂查询(一对多)
一个老师对应多个学生
-
实体类
@Data public class Student { private int id; private String name; private int tid; }
@Data public class Teacher { private int id; private String name; private List<Student> students; }
-
Mapper接口
public interface TeacherMapper { List<Teacher> getAllTeachers(); }
-
Mapper.xml文件
- 子查询嵌套查询
<mapper namespace="com.lx.otm.dao.TeacherMapper"> <select id="getAllTeachers" resultMap="TeacherStudent"> select * from teacher </select> <resultMap id="TeacherStudent" type="com.lx.otm.pojo.Teacher"> <result property="id" column="id"></result> <result property="name" column="name"></result> <!-- javaType:List对应的类型是ArrayList ofType:对应的实体类 --> <collection property="students" column="id" javaType="ArrayList" ofType="com.lx.otm.pojo.Student" select="getAllStudent"></collection> </resultMap> <select id="getAllStudent" resultType="com.lx.otm.pojo.Student"> select * from student where tid = #{id} </select> </mapper>
- 联表查询字段映射
Teacher getTeacherById(@Param("tid") int tid);
<select id="getTeacherById" resultMap="TeacherStudent2"> select t.id tid,t.name tname,s.id sid,s.name sname, s.tid stid from student s,teacher t where s.tid = t.id and t.id = #{tid} </select> <resultMap id="TeacherStudent2" type="com.lx.otm.pojo.Teacher"> <result property="id" column="tid"></result> <result property="name" column="tname"></result> <collection property="students" javaType="ArrayList" ofType="com.lx.otm.pojo.Student"> <result property="id" column="sid"></result> <result property="name" column="sname"></result> <result property="tid" column="stid"></result> </collection> </resultMap>
16、复杂查询小结
- association 关联对象 【多对一使用】
- collection 关联集合 【一对多使用】
- javaType & ofType
- javaType : 用来指定pojo实体类中属性的类型
- ofType :pojo实体类中属性是List或者集合类型,ofType用来指定泛型中约束的类型
- 注意点:
- 保证sql的可读性,尽量通俗易懂,易于维护
- 注意一对多和多对一中,属性和字段名的问题
- 如果不好排查错误,可以使用日志,打印出sql排查,建议使用Log4j
17、动态sql
什么是动态sql?
-
动态SQL是一种在运行时构建和执行SQL语句的技术。
-
通常情况下,静态SQL是预先定义好的SQL语句,而动态SQL允许根据程序运行时的条件和需求来动态地生成SQL语句。
-
动态SQL的主要优点是灵活性和适应性。它可以根据不同的情境和需求生成不同的SQL语句,从而提供更高的灵活性和可重用性。
17.1、环境搭建
-
创建表
CREATE TABLE `blog` ( `id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键', `title` varchar(255) DEFAULT NULL COMMENT '标题', `author` varchar(255) DEFAULT NULL COMMENT '作者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `views` int DEFAULT NULL COMMENT '浏览量', PRIMARY KEY (`id`) )
-
创建实体类
@Data @AllArgsConstructor @NoArgsConstructor public class Blog { private String id; private String title; private String author; private Date createTime; private int views; }
-
编写生成ID的工具类
public class GenerateIdUtils { public static String getId() { return UUID.randomUUID().toString().replaceAll("-", ""); } }
-
mybatis-config.xml
核心配置文件中加入mapUnderscoreToCamelCase为true,开启下划线转驼峰命名<settings> <!--开启下划线转驼峰--> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
-
插入数据以及测试是否成功
public interface BlogMapper { int addBlog(Blog blog); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.lx.dao.BlogMapper"> <insert id="addBlog"> insert into blog values (#{id},#{title},#{author},#{createTime},#{views}) </insert> </mapper>
@Test public void testAddBlog(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); mapper.addBlog(new Blog(GenerateIdUtils.getId(),"学习Mybatis很快乐","lx",new Date(),1000)); mapper.addBlog(new Blog(GenerateIdUtils.getId(),"学习Spring很快乐","lx",new Date(),9999)); mapper.addBlog(new Blog(GenerateIdUtils.getId(),"学习SpringBoot很快乐","lx",new Date(),520)); mapper.addBlog(new Blog(GenerateIdUtils.getId(),"学习Mybatis-plus很快乐","lx",new Date(),10)); sqlSession.close(); }
注意开启自动提交才会提交数据
17.2、动态sql之if标签
List<Blog> queryBlogByIf(Map map);
<select id="queryBlogByIf" resultType="com.lx.pojo.Blog" parameterType="map">
select * from blog where 1=1
<if test="title != '' and title != null">
and title = #{title}
</if>
<if test="author != '' and author != null">
and author = #{author}
</if>
</select>
17.3、动态sql之where标签
上面的where 1=1写法也可以实现,但是这种写法不够好,可以使用where
标签对其优化
<select id="queryBlogByIf" resultType="com.lx.pojo.Blog" parameterType="map">
select * from blog
<where>
<if test="title != '' and title != null">
and title = #{title}
</if>
<if test="author != '' and author != null">
and author = #{author}
</if>
</where>
</select>
where元素只会在子元素有条件成立的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where元素也会很智能的将它们去除,如果子元素没有返回,那么mybatis也会自动将where元素去除。
17.4、动态sql之choose标签
<select id="queryBlogByChoose" resultType="com.lx.pojo.Blog" parameterType="map">
select * from blog
<where>
<choose>
<when test="title != '' and title != null">
and title = #{title}
</when>
<when test="author != '' and author != null">
and author = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>
</select>
choose标签类似于java中的switch。当choose下第一个条件满足,则只会拼接第一个条件查询。第一个不满足,就往下接着走,直到有一个条件满足,则直接拼接查询条件,别的自动过滤掉。如果所有条件都不满足,则会拼接otherwise中的条件
17.5、动态sql之set标签
<update id="updateBlog">
update blog
<set>
<if test="title != '' and title != null">
title = #{title},
</if>
<if test="author != '' and author != null">
author = #{author},
</if>
</set>
where id = #{id}
</update>
set 元素可以用于动态包含需要更新的列,条件不满足则忽略其它不更新的列。set元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
17.6、动态sql之sql标签
<!--有时候一些代码可以公用,可以通过sql标签提取出来-->
<sql id="if-title-author">
<if test="title != '' and title != null">
and title = #{title}
</if>
<if test="author != '' and author != null">
and author = #{author}
</if>
</sql>
<!--使用的时候可以使用include标签,通过refid引用对应的sql标签-->
<select id="queryBlogByIf" resultType="com.lx.pojo.Blog" parameterType="map">
select * from blog
<where>
<include refid="if-title-author"></include>
</where>
</select>
sql标签可以将共用的部分条件等提取出来,别的地方通过include标签中的refid指定对应的sqlid即可
17.7、动态sql之foreach标签
- foreach中or条件查询
<!--
collection:集合的名称
item:集合中每一个元素
open:以什么什么开始
separator:以什么分隔
close:以什么什么结束
-->
<select id="queryBlogByForeachOr" resultType="com.lx.pojo.Blog" parameterType="java.util.Map">
select * from blog
<where>
<foreach collection="ids" item="i" open="and (" separator="or" close=")">
id = #{i}
</foreach>
</where>
</select>
- foreach中in条件查询
<select id="queryBlogByForeachIn" resultType="com.lx.pojo.Blog" parameterType="java.util.Map">
select * from blog
<where>
id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</where>
</select>
@Test
public void testQueryBlogByForeach(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
List<String> ids = new ArrayList<>();
ids.add("1");
ids.add("2");
ids.add("3");
Map<Object, Object> map = new HashMap<>();
map.put("ids",ids);
List<Blog> blogs = mapper.queryBlogByForeach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
}
所谓的动态sql,本质还是sql语句,只是我们可以在sql层面,去执行一个代码逻辑
动态sql,就是根据不同的条件生成不同的sql语句
动态sql,就是在拼接sql语句,我们只要保证sql的正确性,按照sql格式,去排列组合就可以了
18、缓存
18.1、简介
- 什么是缓存【Cache】?
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存(内存)中,用户查询数据就不用从磁盘上查询,将从缓存中查数据,从而提高查询效率,解决了高并发系统的性能问题
- 为什么使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统的效率
- 什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据
18.2、Mybatis缓存
- MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 缓存可以极大的提升查询效率。
- Mybatis定义了两级缓存:一级缓存和二级缓存
- 默认开启的一级缓存。(SqlSession级别缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存
- 为了提高可扩展性,Mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存
18.3、一级缓存
一级缓存也叫本地缓存:
- 与数据库同一次会话期间查询到的数据会放到本地缓存中
- 期间如果需要查询相同的数据,直接从缓存中拿
测试:开启日志,查看查找相同的用户,sql执行几次
@Test
public void testQueryUsers(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserCacheMapper mapper = sqlSession.getMapper(UserCacheMapper.class);
User user1 = mapper.queryUser(1);
System.out.println(user1);
System.out.println("==================================");
User user2 = mapper.queryUser(1);
System.out.println(user1);
System.out.println("==================================");
//只会执行一个sql查询,因为查询的相同用户,第二个用户直接从缓存中取,输出true
System.out.println(user1==user2);
sqlSession.close();
}
@Test
public void testQueryUsers(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserCacheMapper mapper = sqlSession.getMapper(UserCacheMapper.class);
User user1 = mapper.queryUser(1);
System.out.println(user1);
System.out.println("==================================");
//清空缓存,此时再查相同的用户,会走两次查询
sqlSession.clearCache();
//中间这里进行任何增删改,也都会触发清除缓存
User user2 = mapper.queryUser(1);
System.out.println(user1);
System.out.println("==================================");
//会执行两次sql,因为中间清除了缓存,输出false
System.out.println(user1==user2);
sqlSession.close();
}
小结:
-
一级缓存是默认开启的,只在一次SqlSession中有效,也就是拿到数据库连接到关闭连接中间的区间段
-
所有 select 语句的结果将会被缓存。
-
所有 insert、update 和 delete 语句会刷新缓存。
-
缓存不会定时进行刷新(也就是说,没有刷新间隔)。
-
可以通过sqlSession.clearCache();清除缓存
18.4、二级缓存
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
- 工作机制
- 一个会话查询一条数据,这个数据就会被放到当前会话的一级缓存
- 如果当前会话关闭了,那么一级缓存也就会消失,但我们想要的是,即使会话关闭了,一级缓存的数据会被保存到二级缓存
- 新的会话查询信息,就可以从二级缓存中读取获取内容
- 不同的mapper查出来的数据会被放到不同的缓存(map)中
开启二级缓存方法:
mybatis-config.xml
核心配置文件中settings标签加入此配置
<!--虽然缓存是默认开启的,但是也可以显示的声明一下,让他人知道缓存是开启的-->
<setting name="cacheEnabled" value="true"/>
- 在
mapper.xml
文件中加入cache标签
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
- 测试
@Test
public void testQueryUsers(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserCacheMapper mapper = sqlSession.getMapper(UserCacheMapper.class);
//此时查到的数据会放到以及缓存中
User user1 = mapper.queryUser(1);
System.out.println(user1);
//关闭连接,此时一级缓存消失,会将数据保存到二级缓存
//此时有增删改操作时,一级二级缓存都会消失
sqlSession.close();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserCacheMapper mapper2 = sqlSession2.getMapper(UserCacheMapper.class);
User user2 = mapper2.queryUser(1);
System.out.println(user1);
//会执行一次sql,因为是从同一个mapper.xml文件中读取的,数据将从二级缓存中读取,结果为true
System.out.println(user1==user2);
sqlSession2.close();
}
小结:
- 只要开启了二级缓存,在同一个mapper下都有效
- 所有的数据都会先放在一级缓存中
- 只有当会话提交,或者关闭的时候,才会提交到二级缓存中
18.5、缓存原理
- 先会访问二级缓存
- 再访问一级缓存
- 访问数据库
- 将数据临时放到一级缓存
- 如果下面有增删改操作时,当前对应的namespace的mapper将会重置缓存
- 如果没有增删改操作,SqlSession会话结束,数据将会存到二级缓存
- 再访问接着从二级缓存中取,依次循环
18.6、调优
缓存会将select的数据存起来,但是一个接口如果频繁被调用,放缓存会有问题,不想缓存,可以加上useCache=false
<select id="queryUser" resultType="com.lx.pojo.User" useCache="false">
select * from `user` where id = #{id}
</select>
增删改操作会刷新缓存,如果有业务场景需要,不刷新缓存,也可以使用这个属性flushCache=“false”
<insert id="addUser" flushCache="false">
insert into `user` values (#{id},#{name},#{pwd})
</insert>