文章友情转载于:https://blog.csdn.net/qq_42146402/article/details/127701358
本文为SSM框架 【Mybatis】 相关知识,MyBatis 是一款优秀的半自动的ORM持久层框架,下边将对Mybatis的简介
、Mybatis的CRUD实现
,Mybatis的配置文件
,Mybatis的日志配置
,resultMap详解
,分页实现
,注解式开发
,Lombok的使用
,关联映射
,动态SQL
,Mybatis缓存
等进行详尽介绍~
Are you ready❓ Let’s Go ❗️
博主主页:小新要变强 的主页
Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
Java微服务开源项目可参考:企业级Java微服务开源项目(开源框架,用于学习、毕设、公司项目、私活等,减少开发工作,让您只关注业务!)
Mybatis的Maven仓库下载地址: https://mvnrepository.com/artifact/org.mybatis/mybatis
Mybatis的GitHub地址: https://github.com/mybatis/mybatis-3/releases
Mybatis的中文文档地址: htpps://mybatis.org/mybatis-3/zh/index.html
持久化是将程序数据在持久状态和瞬时状态间转换的机制。通俗的讲,就是瞬时数据(比如内存中的数据,是不能永久保存的)持久化为持久数据(比如持久化至数据库中,能够长久保存)。
对于分层的概念我们知道:
持久层的工作:
ORM,即Object-Relational Mapping(对象关系映射),它的作用是在关系型数据库和业务实体对象之间作一个映射,这样,我们在具体的操作业务对象的时候,就不需要再去和复杂的SQL语句打交道,只需简单的操作对象的属性和方法。
很多人青睐 MyBatis ,原因是其提供了便利的 SQL 操作,自由度高,封装性好…… JPA对复杂 SQL 的支持不好,没有实体关联的两个表要做 join 的确要花不少功夫。
sql语句与代码分离,存放于xml配置文件中:
用逻辑标签控制动态SQL的拼接:
查询的结果集与java对象自动映射:
编写原生SQL:
最重要的一点,使用的人多!公司需要!
实现步骤:搭建环境—>导入MyBatis—>编写相关代码—>测试
(1)搭建数据库
-- 创建数据库
create database `mybatis`;
use mybatis;
-- 创建表
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=utf8mb4;
-- 插入数据
insert into `user`(`id`,`name`,`pwd`) values
(1,'张三','123'),
(2,'李四','123'),
(3,'王五','123');
(2)新建一个普通maven项目作为父项目,并导入sql驱动,mybatis,junit组件
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.46version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.2version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
(3)新建一个新组件作为子级项目,普通maven的module
(4)添加配置文件
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<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}"/> //数据库名字,默认root
<property name="password" value="${password}"/> //数据库密码,自己的数据库密码,一般为root
dataSource>
environment>
environments>
configuration>
MybatisUtils.java:
//SqlSessionFactory --生产--> SqlSession
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory; //提升作用域
//获取工厂,固定代码
static {
try {
String resource="mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//获取sqlSession
//SqlSession完全包含了面向对象数据库执行SQL命令所需的方法
public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession();}
}
(1)实体类
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 + '\'' +
'}';
}
}
(2)Dao接口
public interface UserDao {
List<User> getUserList();
}
(3)xxxMapper.xml配置文件
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wang.dao.UserDao">
<select id="getUserList" resultType="com.wang.pojo.User">
select * from mybatis.user where id = #{id}
select>
mapper>
每一个Mapper.xml文件都需要在src->main->resources目录下的mybatis-config.xml核心配置文件中注册:
<mappers>
<mapper resource="com/wang/dao/UserMapper.xml">
mappers>
public class UserDaoTest {
@Test
public void test(){
//获取SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//获取mapper
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> list = mapper.getUserList();
for (User u:list){
System.out.println(u);
}
//不推荐使用以下方式
/*
这种方式能够正常工作,对使用旧版本 MyBatis 的用户来说也比较熟悉。但现在有了一种更简洁的方 式——使用和指定语句的参数和返回值相匹配的接口(比如 BlogMapper.class),现在你的代码不仅更清晰,更加 类型安全,还不用担心可能出错的字符串字面值以及强制类型转换。
*/
// List list = sqlSession.selectList("com.qian.dao.UserDao.getUserList");
// for (User user : list) {
// System.out.println(user);
// }
//关闭SqlSession
sqlSession.close();
}
}
public interface UserMapper {
//查询全部用户
List<User> getUserList();
//根据id查询用户
User getUserById(int id);
//增加新的用户
boolean insertNewUser(User u);
//删除用户
boolean deleteUserById(int id);
boolean deleteUserByName(String name);
//修改用户
boolean updateUserById(User u);
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wang.dao.UserMapper">
<select id="getUserList" resultType="com.wang.pojo.User">
select * from mybatis.user
select>
<select id="getUserById" parameterType="int" resultType="com.wang.pojo.User">
select * from mybatis.user where id=#{id}
select>
<insert id="insertNewUser" parameterType="com.wang.pojo.User">
insert into mybatis.user (id, name, pwd) VALUES (#{id},#{name},#{pwd})
insert>
<delete id="deleteUserById" parameterType="int">
delete from mybatis.user where id=#{id}
delete>
<delete id="deleteUserByName" parameterType="String">
delete from mybatis.user where name=#{name}
delete>
<update id="updateUserById" parameterType="com.wang.pojo.User">
update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id}
update>
mapper>
public class UserDaoTest {
@Test
public void test(){
//获取SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
try{
//获取mapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 查询全表
List<User> list = mapper.getUserList();
for (User u:list){
System.out.println(u);
}
//根据id查询
User user = mapper.getUserById(1);
System.out.println(user);
//插入新用户,注意:更新,插入,删除都需要提交事务
User user1 = new User(4,"李四","25615");
boolean isInserted = mapper.insertNewUser(user1);
sqlSession.commit();
//代码优化
if (mapper.insertNewUser(new User(4,"李四","25615"))) sqlSession.commit();
//删除用户
if (mapper.deleteUserById(4))sqlSession.commit();
if (mapper.deleteUserByName("李四"))sqlSession.commit();
//修改用户
if (mapper.updateUserById(new User(4,"王五","6849816")))sqlSession.commit();
}finally {
//关闭SqlSession
sqlSession.close();
}
}
}
map可以代替任何的实体类,所以当我们数据比较复杂时,可以适当考虑使用map来完成相关工作。
(1)写方法
UserMapper.java:
User getUser(Map<String,Object> map);
boolean addUser(Map<String,Object> map);
(2)写sql
UserMapper.xml:
<select id="getUser" parameterType="map" resultType="com.qian.pojo.User">
select * from mybatis.user where id=#{userId}
select>
<insert id="addUser" parameterType="map">
insert into mybatis.user (id, name, pwd) VALUES (#{userId},#{userName},#{password})
insert>
(3)测试
Test.java:
@Test
public void test(){
//获取SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<String, Object>();
map.put("userId",5);
User user = mapper.getUser(map);
System.out.println(user);
map.put("userId",5);
map.put("userName","孙悟空");
map.put("password","123456");
if (mapper.addUser(map)) sqlSession.commit();
sqlSession.close();
}
方案一:在Java代码中拼串
Test.java:
string name = "%IT%";
list<name> names = mapper.getUserByName(name);
UserMapper.xml:
<select id= "getUsersByName">
select * from user where name like #{name}
select>
方案二:在配置文件中拼接1
Test.java:
string name = "IT";
list<User> users = mapper.getUserByName(name);
UserMapper.xml:
<select id= "getUsersByName">
select * from user where name like "%"#{name}"%"
select>
方案三:在配置文件中拼接2
Test.java:
string name = "IT";
list<User> users = mapper.getUserByName(name);
UserMapper.xml:
<select id= "getUsersByName">
select * from user where name like "%${name}%"
select>
核心配置文件mybatis-config.xml包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文件的层次结构如下(顺序不能乱):
1.properties是一个配置属性的元素
2.settings设置,mybatis最为复杂的配置也是最重要的,会改变mybatis运行时候的行为
3.typeAliases别名(在TypeAliasRegistry中可以看到mybatis提供了许多的系统别名)
4.typeHandlers 类型处理器(比如在预处理语句中设置一个参数或者从结果集中获取一个参数时候,都会用到类型处理器,在TypeHandlerRegistry中定义了很多的类型处理器)
5.objectFactory 对象工厂(myabtis在构建一个结果返回的时候,会使用一个ObjectFactory去构建pojo)
6.plugins 插件
7.environments 环境变量
environment 环境变量
transactionManager 事务管理器
dataSource 数据源
databaseIdProvider 数据库厂商标识
8.mappers 映射器
下面针对几个重要的元素environments,properties,mappers,typeAliases,settings进行讲解。
environments可以为mybatis配置多环境运行,将SQL映射到多个不同的数据库上,必须指定其中一个为默认运行环境(通过default指定),如果想切换环境修改default的值即可。
最常见的就是,生产环境和开发环境,两个环境切换必将导致数据库的切换。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
<environment id="product">
<transactionManager type="JDBC">
<property name="..." value="..."/>
transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
environments>
type="[UNPOOLED|POOLED|JNDI]")
- unpooled:这个数据源的实现只是每次被请求时打开和关闭连接。
- pooled:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来 , 这是一种使得并发 Web 应用快速响应请求的流行处理方式。
- jndi:这个数据源的实现是为了能在如 Spring 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
数据库连接信息我们最好放在一个单独的文件中。
(1)在资源目录下新建一个db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/ssm?useSSL=true&useUnicode=true&characterEncoding=utf8
username=root
password=root
(2)将db.properties文件导入核心配置文件
<configuration>
<properties resource="db.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>
environments>
configuration>
mappers的存在就是要对写好的mapper和xml进行统一管理,要不然系统怎么知道我们写了哪些mapper。
(1)常用引入方式
<mappers>
<mapper resource="com/ydlclass/dao/userMapper.xml"/>
<mapper class="com.ydlclass.dao.AdminMapper"/>
mappers>
(2)在Mapper文件里进行绑定
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ydlclass.mapper.UserMapper">
mapper>
namespace中文意思:命名空间,作用是namespace的命名必须跟某个接口同名,这才能找到实现绑定。
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
在mybatis-config.xml -> configuration进行配置:
<typeAliases>
<typeAlias type="com.wang.pojo.User" alias="user">typeAlias>
<package name="com.wang.pojo"/>
typeAliases>
settings能对我的一些核心功能进行配置,如懒加载、日志实现、缓存开启关闭等。
简单参数说明:
参数 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 该配置影响的所有映射器中配置的缓存的全局开关。 | true /false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 | true / false | false |
useColumnLabel | 使用列标签代替列名。不同的驱动在这方面会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 | true / false | true |
useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要驱动兼容。如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。 | true / false | False |
defaultStatementTimeout | 设置超时时间,它决定驱动等待数据库响应的秒数。 | Any positive integer | Not Set (null) |
mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 | true / false | False |
logPrefix | 指定 MyBatis 增加到日志名称的前缀。 | Any String | Not set |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J / LOG4J / LOG4J2 / JDK_LOGGING / COMMONS_LOGGING / STDOUT_LOGGING / NO_LOGGING | Not set |
完整的 settings 元素,有很多可以配置的选项(可以自行了解):
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
settings>
配置日志的一个重要原因是想在调试的时候能观察到sql语句的输出,能查看中间过程
指定 MyBatis 应该使用哪个日志记录实现。如果此设置不存在,则会自动发现日志记录实现。
STDOUT_LOGGING:标准输出日志。
<configuration>
<properties resource="db.properties"/>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
settings>
... ...
<configuration>
(1)导入log4j的包
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>${logback.version}version>
dependency>
(2)log4j.properties配置文件
<configuration>
<property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss} %c [%thread] %-5level %msg%n"/>
<property name="log_dir" value="d:/logs" />
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<target>System.outtarget>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}pattern>
encoder>
appender>
<appender name="file" class="ch.qos.logback.core.FileAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}pattern>
encoder>
<file>${log_dir}/sql.logfile>
appender>
<root level="ALL">
<appender-ref ref="console"/>
root>
<logger name="mybatis.sql" level="debug" additivity="false">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
logger>
configuration>
(3)setting设置日志实现
<settings>
<setting name="logImpl" value="SLF4J"/>
settings>
如果数据库字段和实体的字段是一一对应,那么MyBatis会【自动映射】,但是如果不一致,比如一个叫user一个叫username,那么就需要我们手动的建立一一映射的关系了。
(1)Java实体类
public class User {
private int id; //id
private String name; //姓名,数据库为username
private String password; //密码,一致
//构造
//set/get
//toString()
}
(2)mapper接口
//根据id查询用户
User selectUserById(int id);
(3)mapper映射文件
<select id="selectUserById" resultType="user">
select * from user where id = #{id}
select>
(4)测试与结果分析
Test.java:
@Test
public void testSelectUserById() {
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectUserById(1);
System.out.println(user);
session.close();
}
结果:
User{id=1, name='null', password='123'}
查询出来发现 name为空 . 说明出现了问题!
分析:
解决方案:
方案一:为列名指定别名 , 别名和java实体类的属性名一致 。
<select id="selectUserById" resultType="User">
select id , username as name ,password from user where id = #{id}
select>
方案二:使用结果集映射->ResultMap 【推荐】
<resultMap id="UserMap" type="User">
<id column="id" property="id"/>
<result column="username" property="name"/>
<result column="password" property="password"/>
resultMap>
<select id="selectUserById" resultMap="UserMap">
select id , username , password from user where id = #{id}
select>
结论:
这个地方我们手动调整了映射关系,称之为【手动映射】。但如果不调整呢? MyBatis当然会按照约定自动映射。
当然约定的最基本的操作就是全部都一样,还有就是下划线和驼峰命名的自动转化。
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
分页(Paging):即有很多数据,我们就需要分页来分割数据,可提高整体运行性能,增强用户使用体验需求等。
不使用分页将遇到的问题:
语法
-- 语法:select * from xxx limit startIndex,pageSize
select * from user limit 3;
mybatis的sql语句如果有多个参数,需要用map封装。
Mapper接口
List<User> selectLimit(Map<String,Integer> map);
xxxMapper.xml
<select id="selectLimit" parameterType="map" resultMap="UserMap">
select * from mybatis.user limit #{startIndex},#{pageSize}
select>
测试
Test.java:
public class UserDaoTest {
@Test
public void limitTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("startIndex",0);
map.put("pageSize",2);
List<User> list=mapper.selectLimit(map);
for (User u:
list) {
System.out.println(u);
}
sqlSession.close();
}
}
这种方法官方不推荐。
Mapper接口
List<User> selectRowBounds();
xxxMapper.xml
<select id="selectRowBounds" resultMap="UserMap">
select * from mybatis.user
select>
测试
@Test
public void selectRowBounds(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
RowBounds rowBounds = new RowBounds(0,2);
List<User> list = sqlSession.selectList("com.wang.dao.UserMapper.selectRowBounds"
,null,rowBounds);
for (User user : list) {
System.out.println(user);
}
sqlSession.close();
}
Mapper接口
@Select("select * from mybatis.user")
List<User> selectAll();
注册绑定
mybatis-config.xml:
<mappers>
<mapper class="com.wang.dao.UserMapper"/>
mappers>
测试
@Test
public void selectAll(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
//底层主要应用反射
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list=mapper.selectAll();
for (User user : list) {
System.out.println(user);
}
sqlSession.close();
}
设置自动提交
MybatisUtils.java:
public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(true); }
Mapper接口
//多个参数情况下,有两种解决方式,一个map封装,另一种是注解Param
@Select("select * from mybatis.user where id=#{id}")
User selectUserById(@Param("id") int id);
@Select("select * from mybatis.user")
List<User> selectAll();
@Insert("insert into mybatis.user() values(#{id},#{name},#{password}) ")
boolean insertUser(User u);
@Update("update user set name=#{name},pwd=#{password} where id = #{id}")
boolean updateUser(User u);
@Delete("delete from mybatis.user where id=#{id}")
boolean deleteUser(@Param("id") int id);
测试
@Test
public void selectAll(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
//底层主要应用反射
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// List list=mapper.selectAll();
// for (User user : list) {
// System.out.println(user);
// }
/**
User u=mapper.selectUserById(1);
System.out.println(u);
*/
// boolean isInserted=mapper.insertUser(new User(4,"图拉真","dgsdgs"));
// if (mapper.updateUser(new User(6,"寒江雪",null)))
if (mapper.deleteUser(6))
for (User user : mapper.selectAll()) {
System.out.println(user);
}
sqlSession.close();
}
这个注解是为SQL语句中参数赋值而服务的。@Param的作用就是给参数命名,比如在mapper里面某方法A(int id),当添加注解后A(@Param(“userId”) int id),也就是说外部想要取出传入的id值,只需要取它的参数名userId就可以了。将参数值传如SQL语句中,通过#{userId}进行取值给SQL的参数赋值。
实例一:@Param注解基本类型的参数
Mapper接口
public User selectUser(@Param("userName") String name,@Param("password") String pwd);
xxxMapper.xml
<select id="selectUser" resultMap="User">
select * from user where user_name = #{userName} and user_password=#{password}
select>
其中where user_name = #{userName} and user_password = #{password}中的userName和password都是从注解@Param()里面取出来的,取出来的值就是方法中形式参数 String name 和 String pwd的值。
实例二:@Param注解JavaBean对象
SQL语句通过@Param注解中的别名把对象中的属性取出来然后复制
Mapper接口
public List<User> getAllUser(@Param("user") User u);
xxxMapper.xml
<select id="getAllUser" parameterType="com.vo.User" resultMap="userMapper">
select
from user t where 1=1
and t.user_name = #{user.userName}
and t.user_age = #{user.userAge}
select>
注意点:
Lombok是一个可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具,通过使用对应的注解,可以在编译源码的时候生成对应的方法。
(1)IDEA左上角File->Settings->Plugins
(2)搜索Lombok,下载安装
(3)导入maven
pom.xml:
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.10version>
dependency>
Lombok的支持:
@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
@var
experimental @var
@UtilityClass
Lombok config system
Code inspections
Refactoring actions (lombok and delombok)
常用支持:
使用方法:
在具体的实体类上加相应的注解。
以多个学生对应一个老师为例,存在:
create table `teacher`(
`id` int not null,
`name` varchar(30) default null,
primary key(`id`)
) engine=InnoDB default charset=utf8;
insert into teacher values (1,'王老师');
create table `student`(
`id` int not null,
`name` varchar(30) default null,
`tid` int not null,
primary key(`id`),
key `FK_tid` (`tid`),
constraint `FK_tid` foreign key(`tid`) references `teacher`(`id`)
) engine=InnoDB default charset=utf8;
(1)导入Lombok
(2)新建Teacher,Student实体类
(3)新建Mapper接口
(4)在resources新建com->xxx->dao文件夹
(5)新建xxxMapper.xml文件
(6)在mybatis-config.xml中注册绑定xxxMapper.xml
(7)在TeacherMapper接口中创建selectAll()方法
(8)在TeacherMapper.xml中写对应的查询
(9)新建测试类,在测试类中测试使用
实体类
Student.java:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private String name;
private Teacher teacher;
}
Teacher.java:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
private int id;
private String name;
}
Mapper接口
List<Student> selectAll();
xxxMapper.xml
<resultMap id="student_teacher" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
resultMap>
<select id="selectAll" resultMap="student_teacher">
select * from mybatis.student
select>
<select id="getTeacher" resultType="Teacher">
select * from mybatis.teacher where id=#{tid}
select>
测试
@Test
public void selectAll(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> studentList = mapper.selectAll();
for (Student s:
studentList) {
System.out.println(s);
}
sqlSession.close();
}
Mapper接口
List<Student> selectAll2();
xxxMapper.xml
<select id="selectAll2" resultMap="S_T">
select s.id sid,s.name sname,t.name tname
from mybatis.student s,mybatis.teacher t
where s.tid=t.id
select>
<resultMap id="S_T" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
association>
resultMap>
测试
@Test
public void selectAll(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> studentList = mapper.selectAll();
for (Student s:
studentList) {
System.out.println(s);
}
sqlSession.close();
}
实体类
Student.java:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private String name;
private int tid;
}
Teacher.java:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
private int id;
private String name;
//老师拥有多个学生
private List<Student> students;
}
Mapper接口
public interface TeacherMapper {
List<Teacher> selectAll();
//获取指定老师下的所有学生
Teacher getTeacher(@Param("tid")int id);
Teacher getTeacher2(@Param("tid")int id);
List<Student> getStudents(@Param("tid")int id);
}
xxxMapper.xml
<select id="selectAll" resultType="Teacher">
select * from mybatis.teacher
select>
<select id="getTeacher" resultMap="S_T">
select t.id tid, t.name tname,s.name sname
from mybatis.teacher t,mybatis.student s
where s.tid=tid and tid=#{tid}
select>
<resultMap id="S_T" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="Student">
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
collection>
resultMap>
<select id="getTeacher2" resultMap="student_teacher">
select * from mybatis.teacher where id=#{tid}
select>
<resultMap id="student_teacher" type="Teacher">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="students" column="id" ofType="Student" select="getStudents"/>
resultMap>
<select id="getStudents" resultType="Student">
select * from mybatis.student where tid=#{tid}
select>
测试
@Test
public void selectAll(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> studentList = mapper.selectAll();
for (Student s:
studentList) {
System.out.println(s);
}
sqlSession.close();
}
MyBatis提供了对SQL语句动态的组装能力,大量的判断都可以在 MyBatis的映射XML文件里面配置,以达到许多我们需要大量代码才能实现的功能,大大减少了我们编写代码的工作量。
动态SQL的元素:
元素 | 作用 | 备注 |
---|---|---|
if | 判断语句 | 单条件分支判断 |
choose、when、otherwise | 相当于Java中的 case when语句 | 多条件分支判断 |
trim、where、set | 辅助元素 | 用于处理一些SQL拼装问题 |
foreach | 循环语句 | 在in语句等列举条件常用 |
if元素相当于Java中的if语句,它常常与test属性联合使用。现在我们要根据name去查找学生,但是name是可选的,如下所示:
<select id="findUserById" resultType="com.wang.entity.User">
select id,username,password from user
where 1 =1
<if test="id != null">
AND id = #{id}
if>
<if test="username != null and username != ''">
AND username = #{username}
if>
<if test="password != null and password != ''">
AND password = #{password}
if>
select>
上面的select语句我们加了一个1=1
的绝对true的语句,目的是为了防止语句错误,变成SELECT * FROM student WHERE
这样where后没有内容的错误语句。这样会有点奇怪,此时可以使用 < where>
元素。
<select id="findUserById" resultType="com.wang.entity.User">
select id,username,password from user
<where>
<if test="id != null">
AND id = #{id}
if>
<if test="username != null and username != ''">
AND username = #{username}
if>
<if test="password != null and password != ''">
AND password = #{password}
if>
where>
select>
有时候我们要去掉一些特殊的SQL语法,比如常见的and、or,此时可以使用trim元素。trim元素意味着我们需要去掉一些特殊的字符串,prefix代表的是语句的前缀,而prefixOverrides代表的是你需要去掉的那种字符串,suffix表示语句的后缀,suffixOverrides代表去掉的后缀字符串。
<select id="select" resultType="com.wang.entity.User">
SELECT * FROM user
<trim prefix="WHERE" prefixOverrides="AND">
<if test="username != null and username != ''">
AND username LIKE concat('%', #{username}, '%')
if>
<if test="id != null">
AND id = #{id}
if>
trim>
select>
有些时候我们还需要多种条件的选择,在Java中我们可以使用switch、case、default语句,而在映射器的动态语句中可以使用choose、when、otherwise元素。
<select id="select" resultType="com.wang.entity.User">
SELECT * FROM user
WHERE 1=1
<choose>
<when test="name != null and name != ''">
AND username LIKE concat('%', #{username}, '%')
when>
<when test="id != null">
AND id = #{id}
when>
choose>
select>
在update语句中,如果我们只想更新某几个字段的值,这个时候可以使用set元素配合if元素来完成。注意: set元素遇到,会自动把,去掉。
<update id="update">
UPDATE user
<set>
<if test="username != null and username != ''">
username = #{username},
if>
<if test="password != null and password != ''">
password = #{password}
if>
set>
WHERE id = #{id}
update>
foreach元素是一个循环语句,它的作用是遍历集合,可以支持数组、List、Set接口。
<select id="select" resultType="com.wang.entity.User">
SELECT * FROM user
WHERE id IN
<foreach collection="ids" open="(" close=")" separator="," item="id">
#{id}
foreach>
select>
<insert id="batchInsert" parameterType="list">
insert into `user`( user_name, pass)
values
<foreach collection="users" item="user" separator=",">
(#{user.username}, #{user.password})
foreach>
insert>
有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。
提取SQL片段:
<sql id="if-title-author">
<if test="title != null">
title = #{title}
if>
<if test="author != null">
and author = #{author}
if>
sql>
引用SQL片段:
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<include refid="if-title-author">include>
where>
select>
Mybatis系统默认定义了两级缓存:
一级缓存是sqlsession级别的缓存
一级缓存工作原理
一级缓存工作原理图解:
一级缓存测试步骤
一级缓存演示
Mapper接口:
User getUserById(int id);
xxxMapper.xml:
<select id="getUserById" parameterType="int" resultType="User">
select * from mybatis.user where id=#{id}
select>
Test.java:
@Test
public void getUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User u=mapper.getUserById(1);
System.out.println(u);
System.out.println("=============");
User user=mapper.getUserById(1);
System.out.println(user);
System.out.println(u==user);
sqlSession.close();
}
缓存失效的情况
一级缓存生命周期
二级缓存是mapper级别的缓存
二级缓存实现原理
<settings>
<setting name="cacheEnabled" value="true"/>
settings>
<cache eviction="LRU" flushInterval="100000"/>
禁用二级缓存
在statement中可以设置useCache=false,禁用当前select语句的二级缓存,默认情况为true
<select id="getStudentById" parameterType="java.lang.Integer" resultType="Student" useCache="false">
在实际开发中,针对每次查询都需要最新的数据sql,要设置为useCache=“false” ,禁用二级缓存。
flushCache标签:刷新缓存(清空缓存)
<select id="getStudentById" parameterType="java.lang.Integer" resultType="Student" flushCache="true">
一般下执行完commit操作都需要刷新缓存,flushCache="true 表示刷新缓存,可以避免脏读。
二级缓存应用场景
二级缓存局限性
MyBatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用MyBatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为MyBatis的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。
使用二级缓存
(1)开启全局缓存
<setting name="cacheEnabled" value="true"/>
(2)在要使用二级缓存的Mapper.xml中,写标签
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
(3)测试
@Test
public void getUserById2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User u=mapper.getUserById(1);
System.out.println(u);
sqlSession.close();
System.out.println("============");
User user = mapper2.getUserById(1);
System.out.println(user==u);
sqlSession2.close();
}
(2)问题
我们需要实体类序列化,否则会抛出异常
(4)总结
EhCache简介
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认CacheProvider。Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。
EhCache使用
(1)导包
<dependency>
<groupId>org.mybatis.cachesgroupId>
<artifactId>mybatis-ehcacheartifactId>
<version>1.2.2version>
dependency>
(2)写入配置文件(resources->ehcache.xml)
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<diskStore path="java.io.tmpdir/ehcache"/>
<defaultCache
maxEntriesLocalHeap="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxEntriesLocalDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
defaultCache>
<cache name="HelloWorldCache"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="5"
timeToLiveSeconds="5"
overflowToDisk="false"
memoryStoreEvictionPolicy="LRU"/>
ehcache>
(3)在Mapper中指定
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
(4)测试
@Test
public void getUserById2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User u=mapper.getUserById(1);
System.out.println(u);
sqlSession.close();
System.out.println("============");
User user = mapper2.getUserById(1);
System.out.println(user==u);
sqlSession2.close();
}
自定义缓存
只要实现了org.apache.ibatis.cache.Cache接口,就能定义自己的缓存,但是实现比较复杂,只需要会使用就行,ehcache是继承了AbstractEhcacheCache,该类已经实现了Cache接口。
public class MyCache implements Cache {
@Override
public String getId() {
return null;
}
@Override
public void putObject(Object key, Object value) {
}
@Override
public Object getObject(Object key) {
return null;
}
@Override
public Object removeObject(Object key) {
return null;
}
@Override
public void clear() {
}
@Override
public int getSize() {
return 0;
}
}
实际开发中使用的缓存
在实际开发中,我们更多的使用Redis来做缓存。
Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
Java微服务开源项目可参考:企业级Java微服务开源项目(开源框架,用于学习、毕设、公司项目、私活等,减少开发工作,让您只关注业务!)