本文是博主初步学习MyBatis所做的笔记,当中如有错别字、描述不对的地方或着侵权的地方,恳请及时告知,博主亦会虚心接收,及时更正
- 博主主页:知识汲取者
- 博主Gitee仓库:Gitee
- 博主Github仓库:Github
欢迎大家访问支持<(^-^)>
Mabatis是什么?
MyBatis 是一款优秀的持久层1框架2(也是一个半自动的ORM,Object Relation Mapping框架),它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。
官网:mybatis – MyBatis 3 | 简介
Mabatis的优点:
1)提高代码的可维护性。Mabatis让SQL语句和代码进行了解耦3,SQL语句写在XML文件中想修改SQL语句只需要修改XML文件而不必去修改代码
2)减少重复代码。与JDBC相比,减少了50%以上的代码量,消除了JDBC 大量冗余的代码,不需要手动开关连接
3)具有良好的兼容性。因为MyBatis 使用JDBC 来连接数据库,所以只要JDBC 支持的数据库MyBatis 都支持
4)简单易学。本身就很小且简单,没有任何第三方依赖,最简单安装只要两个jar文件+配置几个SQL映射文件。
5)灵活轻便。MyBatis不会对应用程序或者数据库的现有设计强加任何影响。 SQL语句写在XML文件里,便于统一管理和优化。通过SQL语句可以满足操作数据库的所有需求。PS:MyBatis还具有强大的动态SQL能力
……
Mabatis的历史
MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了[google code](https://baike.baidu.com/item/google code/2346604),并且改名为MyBatis。2013年11月迁移到Github。iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)。
拓展:JavaEE的三层架构
任务:使用MyBatis读取数据库中的User表中的所有数据,并以日志文件的形式打印到控制台上
Step1:在数据库中创建User表
create database mybatis;
use mybatis;
drop table if exists tb_user;
create table tb_user(
id int primary key auto_increment,
username varchar(20),
password varchar(20),
gender char(1),
addr varchar(30)
);
INSERT INTO tb_user VALUES (1, 'zhangsan', '123', '男', '北京');
INSERT INTO tb_user VALUES (2, '李四', '234', '女', '天津');
INSERT INTO tb_user VALUES (3, '王五', '11', '男', '西安');
Step2:创建Maven项目模块
Step3:定义POJO4(Plain Ordinary Java Object)类
User类:
package com.hhxy.pojo;
public class User {
private Integer id;
private String username;
private String password;
private String gender;
private String addr;
public User() {
}
public User(Integer id, String username, String password, String gender, String addr) {
this.id = id;
this.username = username;
this.password = password;
this.gender = gender;
this.addr = addr;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", gender='" + gender + '\'' +
", addr='" + addr + '\'' +
'}';
}
}
备注:
- 编写Maven配置文件,主要是用来导入项目所依赖的jar包
- 编写MyBatis核心配置文件,主要是实现MaBatis框架的核心功能,包括数据库连接、关联SQL映射文件(暂时先不管,后续深入了解)需要注意:该配置文件的名字一定不能乱改!!!
- 编写SQL映射文件,该文件中存放Java代码要执行的SQL语句,实现了SQL语句和代码进行了解耦
- 编写Logback核心配置文件,主要是用于方便查看使用MyBatis框架执行程序后的具体细节
这些配置文件的编写顺序没有要求,但是配置文件的编写肯定是要在编写Java代码前的 (其实这些文件CV一下就行了)
推荐阅读:
- 一文带你快速上手Maven
- Logback快速入门
Step1:编写Maven配置文件
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.hhxygroupId>
<artifactId>day3_mybatisartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.5version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.27version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.20version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.3version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-coreartifactId>
<version>1.2.3version>
dependency>
dependencies>
project>
Step2:编写MyBatis核心配置文件
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="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="32345678"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="UserMapper.xml"/>
mappers>
configuration>
Step3:编写SQL映射文件
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test">
<select id="selectAll" resultType="com.hhxy.pojo.User">
select * from tb_user;
select>
mapper>
Step4:编写Logback核心配置文件
<configuration>
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%level] %blue(%d{HH:mm:ss.SSS}) %cyan([%thread]) %boldGreen(%logger{15}) - %msg %npattern>
encoder>
appender>
<logger name="com.itheima" level="DEBUG" additivity="false">
<appender-ref ref="Console"/>
logger>
<root level="DEBUG">
<appender-ref ref="Console"/>
root>
configuration>
Step5:编写相关Java代码
package com.hhxy.test;
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.InputStream;
import java.util.List;
public class MyBatisDemo {
public static void main(String[] args) throws Exception {
//1、定位xml文件,获取SqlSessionFactory对象
String resource = "./mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2、获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//3、执行SQL语句
List<Object> users = sqlSession.selectList("test.selectAll");
System.out.println(users);
//5、释放资源
sqlSession.close();
}
}
拓展:IDEA设置数据库连接(用于解决SQL映射文件的警告提示 警告提示后没有关键字提示了,用起来很不方便)
- 当鼠标选中mybatis@localhost时,点击可以直接在IDEA中书写SQL语句,比在Navicat中写SQL更舒服,因为IDEA有更强大的关键字提示
- 当鼠标选中表时,点击可以直接设计表
IDEA真的很( ఠൠఠ )ノ
通过MyBatis快速入门,我们可以知道MyBatis优化了原生JDBC的硬编码问题5,但是它的优化不够彻底,当我们使用MyBatis执行SQL语句时,仍然存在硬编码问题,这就需要使用到Mapper代理来进行进一步优化了。
Mapper代理就是定义一个接口,在接口中定义和SQL映射文件的SQL语句相关联的抽象方法,从而在Java代码中只需要使用该接口对象中的方法就能直接访问SQL映射文件,而不需要再通过文件路径来访问SQL映射文件。
Mapper代理的优点:
示例:
注意:
Mapper接口最好和相应的SQL映射文件同名,同时要和SQL映射文件在同一目录下
如果Mapper接口和SQL映射文件同名,可以使用包扫描的方式进化SQL映射文件的加载
Mapper接口中的方法名要和SQL映射文件中中SQL语句标签的id属性值同名,并且参数类型和返回值类型要相同
SQL映射文件中mapper标签的namespace属性值要和Mapper接口的全类名相同
小知识须知:在Maven项目中,按照Maven规范,我们必须将代码(类、接口)和配置文件分开写,但是Mapper接口有需要和SQL映射文件在同一级目录下,就需要利用Maven项目编译的特点,即:当Maven项目被编译后,resources区域的文件和Java代码区域的文件是放在在一个区域进行管理的(可以使用compile命令后,去文件夹中查看验证),这样我们就可以只需要给JMapper接口和其相应的SQL映射文件设置相同的目录结构就可以解决这个问题了。同时需要注意,resources区域是文件目录,不能通过点直接创建目录结构,必须一个一个地进行创建(或者使用/号),而Java代码区域可以直接使用点创建目录结构。
package com.hhxy.test;
import com.hhxy.mapper.UserMapper;
import com.hhxy.pojo.User;
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.InputStream;
import java.util.List;
public class MyBatisDemo {
public static void main(String[] args) throws Exception {
//1、定位xml文件,获取SqlSessionFactory对象
String resource = "./mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2、获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
/*---------------------------------Mapper代理核心代码--------------------------------*/
//3、执行SQL语句
//3.1 获取UserMapper接口对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//3.2 获取SQL语句执行后的返回对象
List<User> users = userMapper.selectAll();
//这段代码的原生写法:List
/*----------------------------------------------------------------------------------*/
System.out.println(users);
//5、释放资源
sqlSession.close();
}
}
改动的配置文件(其他的配置文件和MyBatis快速入门的一样):
使用包扫描简化SQL映射文件的加载:
不使用包扫描加载,需要一个一个将Mapper配置文件进行加载,使用包扫描是直接将一个mapper包加载,一次性就将所有的Mapper配置文件进行了加载,更加简化了代码
加载映射文件的常见方式:
<mappers>
<mapper resource="E:\项目\Project_JavaWeb\day3_mybatis\src\main\resources\com\hhxy\mapper\UserMappper.xml"/>
<mapper resource="com\hhxy\mapper\UserMappper.xml">mapper>
<package name="com.hhxy.mapper"/>
mappers>
详情见这里:MyBatis官方文档
注意:核心配置文件的书写顺序要按照上面这个自上而下的顺序,否则会报错,这是xml的约束规范
既可以在mybatis文件中直接配置,也可以在properties文件中进行配置。
在properties中配置:①需要先创建一个properties配置文件,②将mybatis核心配置文件中需要替换的信息以键值对的写在里面,最后直接在mybatis核心配置文件中③使用properties标签引用就行了。这样做的优点能够实现配置信息和配置文件的解耦,从而提高配置文件的灵活性,每次需要修改配置信息,只需要在properties中改动就行了
在mybatis文件中进行配置:
在properties文件中进行配置:
细节:在properties中设置键名时,可以加一个表示功能的前缀,防止键名重复
注意:在properties文件中设置配置信息,一定不要使用转义符,我就是使用了&
的转义符&
导致报错了!但是在mybatis中设置配置信息时,需要使用转义符
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
详情请参考:官方文档
typeAliases
:用于给类或包起别名
1)给类起别名
在映射配置文件中的 resultType 属性需要配置数据封装的类型(类的全限定名)。而每次这样写是特别麻烦的,Mybatis
提供了 类型别名 (typeAliases) 可以简化这部分的书写,需要注意它仅用于 XML 配置,意在降低冗余的全限定类名书写。
<typeAliases>
<package name="com.hhxy.pojo.User"/>
typeAliases>
使用typeAliases
起别名后,就可以将所有MyBatis配置文件中的 com.hhxy.pojo.类名
替换成 类名
(替换后的类名不区分大小写)
2)给包起别名
如果我们的配置文件都是用给类起别名,如果一个包下有很多实体类,要对多个实体类进行起别名,这样就显得很不方便。所以我们可以像之前SQL映射文件的加载一样,我们可以使用包扫描这种思路进行进一步的简化,即:给包起别名
<typeAliases> <!--name属性的值是实体类所在包-->
<package name="com.hhxy.pojo"/>
</typeAliases>
这样com.hhxy.pojo
下所有的类都可以直接使用类名(类名同样不区分大小写)
这里需要注意有一个前提条件,就是:com.hhxy.pojo下的实体类不带有@Alias
,如果带有该注解,就以注解值为准
environments
:用于配置数据库连接的环境信息
可以配置多个环境变量(
environment
),用于连接不同的数据库,通过default
属性进行切换
transactionManager
:设置事务管理器
type:设置事务管理方式,有JDBC和MANAGED两种方式。JDBC表示原生的事务管理方式,可手动也可自动;MANAGED:被管理,例如使用Spring时就设置成MANAGED
dataSource
:设置数据源
type:设置数据源类型,有POOLED、UNPOOLED和JNDI。POOLED表示使用的数据库连接池缓存数据连接;UNPOOLED表示不使用数据库连接池;JNDI表示使用上下文中的数据源。
备注:POOLED
是MyBatis默认使用的数据库连接池
下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。
别名 | 映射的类型 |
---|---|
_byte | byte |
_char (since 3.5.10) | char |
_character (since 3.5.10) | char |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
char (since 3.5.10) | Character |
character (since 3.5.10) | Character |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
biginteger | BigInteger |
object | Object |
date[] | Date[] |
decimal[] | BigDecimal[] |
bigdecimal[] | BigDecimal[] |
biginteger[] | BigInteger[] |
object[] | Object[] |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
每次我们要写mybati配置文件,都需要去CV,这显得很麻烦,所以我们可以通过设置模板,在IDEA中右击直接创建模板,就可以避免去CV了,进一步简化了开发。模板功能YYDS
mybatis-config.xml模板:
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties"/>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
<typeAliases>
<package name="com.hhxy.pojo"/>
typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
dataSource>
environment>
environments>
<mappers>
<package name="com.hhxy.mapper"/>
mappers>
configuration>
mapper配置文件模板:
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hhxy.mapper.Mapper">
mapper>
案例代码已经上传到Gitee和GitHub上了,欢迎大家参观:
- Gitee仓库地址✈️传送门
- GitHub仓库地址 ✈️传送门
查询tb_brand表中的所有数据,并且用集合输出查询的所有数据
Step1:在数据库中建立tb_brand表
-- 删除tb_brand表
drop table if exists tb_brand;
-- 创建tb_brand表
create table tb_brand
(
-- id 主键
id int primary key auto_increment,
-- 品牌名称
brand_name varchar(20),
-- 企业名称
company_name varchar(20),
-- 排序字段
ordered int,
-- 描述信息
description varchar(100),
-- 状态:0:禁用 1:启用
status int
);
-- 添加数据
insert into tb_brand (brand_name, company_name, ordered, description, status)
values ('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0),('华为', '华为技术有限公司', 100, '华为致力于把数字世界带入每个人、每个家庭、每个组织,构建万物互联的智能世界', 1),('小米', '小米科技有限公司', 50, 'are you ok', 1);
SELECT * FROM tb_brand;
这一步可以尝试着在IDEA中进行,记不得步骤的可以回看进行复习
Step2:编写相应的配置文件,同时创建相应的类
1)需要编写的配置文件有:Maven配置文件、Logback配置文件、MyBatis核心配置文件、SQL映射文件。
相应配置文件参考MyBatis入门
2)所需依赖:org.mybatis:mybatis:3.5.5
、mysql:mysql-connector-java:8.0.27
、org.slf4j:slf4j-api:1.7.20
、ch.qos.logback:logback-classic:1.2.3
、ch.qos.logback:logback-core:1.2.3
、junit:junit:4.13
要求:
项目目录结构:
Step3:安装MyBatisX插件
F i l e → S e t t i n g s → P l u g i n s → M a r k e t p l a c e File\rightarrow{Settings}\rightarrow{Plugins}\rightarrow{Marketplace} File→Settings→Plugins→Marketplace
不使用插件,我们在编写Mapper接口的方法时,方法名需要和对应的SQL文件的statement的id值一样,而当我们SQL映射文件中有多个statement时,我们在Mapper接口中写抽象方法时,需要经常跳转到SQL映射文件查看对照,显得很麻烦,而MyBatisX插件就主要是解决这个问题的,让开发变得更加便捷、高效!
MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。
主要功能:
XML映射配置文件和接口方法间相互跳转
根据接口方法生成 statement(直接按
Alt+Enter
)建议去官网下载,不然可能用了一段时间会报错:IDEA官网
拓展:使用Map集合存储查询结果
备注:也可以使用Map集合存储查询结果,一般使用List集合是搭配POJO类,存储的数据是一整条一整条的,字段是固定的,字段名不同需要起别名;而使用Map集合,任何字段都可以(不需要进行字段映射),一般是用来存储查询结果不是实体类
需要注意的是:Map集合存储查询结果时,如果查询结果为null,Map集合不会进行存储
- 方式一:使用List集合存储Map集合
package com.hhxy.test;
import com.hhxy.mapper.BrandMapper;
import com.hhxy.pojo.Brand;
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 org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyBatisTest {
@Test
public void testSelectAll() throws Exception {
//1、加载MyBatis核心配置文件,获取SqlSessionFactory对象
//1.1 确定Mybatis核心配置文件路径
String resource = "./mybatis-config.xml";
//1.2 从resources目录下读取MyBatis配置文件,获取InputStream对象
InputStream is = Resources.getResourceAsStream(resource);
//1.3 获取SqlSessionFactory对象
SqlSessionFactory sqlSF = new SqlSessionFactoryBuilder().build(is);
//2、获取SqlSession对象
SqlSession sqlS = sqlSF.openSession();
//3、执行SQL语句(使用Mapper代理)
//3.1 获取Mapper接口对象
BrandMapper brandMapper = sqlS.getMapper(BrandMapper.class);
/*----------------核心代码(其他的代码可以进行CV)-----------------*/
//3,2 使用Mapper接口对象执行方法
List<Brand> brands = brandMapper.selectAll();
/*--------------------------------------------------------------*/
System.out.println(brands);//打印输出结果,进行检查
//4、释放资源
sqlS.close();
}
}
我们可以发现brandName和companyName都为空,原因是:数据库字段名和实体类的属性名不一致
解决方案:
一对一的情况:
对名称不一致的字段起别名,让别名和对应实体类的属性名一致
缺点:每次查询都要重新定义一次别名,很繁琐
可以使用sql片段进一步简化:
仍存在缺点:存在硬编码(每次字段起别名都是写死的),不灵活!!!(有点无语,直接用第二种方法得了)
备注:sql片段,一般用来替代
*
,因为*
的使用需要被二次编译,一定程度的降低了SQL的效率,所以不建议使用*
好查询所有使用resultMap进行自定义映射
备注:
1)
resultMap
标签中既可以用result
标签,也可以用id
标签。
id
用来完成主键的映射,result
用来完成普通字段的映射2)
cloum
属性的值为数据库字段名,propert
属性的值为实体类的属性名。也类似于起别名,但是只需要将数据库字段名和实体类字段名不一样的进行映射就可以了,而不用重复去写名称一样的字段3)
type
的值表示该SQL语句返回值的类型,type="Brand"就表示返回值的类型是com.hhxy.mapper.Brand类型的数据(这里使用了包扫描,所以前面的包名省略了)使用settings标签进行全局映射:将数据库中下划线的字段映射成Java中的驼峰样式
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> settings>
就能直接将所有表中 下划线字段名 映射(相当于起别名)成 驼峰字段名,例如:brand_name映射成brandName
这个方法最省事(●’◡’●),推荐直接加到mybatis配置文件的模板中
使用这三种方法都能成功解决字段名和属性名不一致的问题:
当遇到多对一(员工表对部门表,多个员工属于同一个部门)时,需要换种方式解决
备注:当一张表有一个外键,则可以通过给对应实体类添加一个引用数据类型来映射外键
多对一的情况:
多对于就是多个对象对应一张表,比如员工表对部门表就是多对一,多个员工属于同一个部门
对一需要设置一个对象类型的数据
- 原始写法:
引入外部申明:本质就是使用
ResultMap
PS:这个方式最为简单,推荐使用
使用级联:
resultMap
+association
:备注:这种方式,需要给表中所有字段与实体类的属性进行映射,否则查询结果会为null,这里age和gender就是,注意:在setting中设置全局的格式转换,也不起作用,仍然需要使用result和id标签进行手动设置,然而在分布查询中如果设置了全局格式转换就不需要在进行手动设置了
分布查询:也需要使用
resultMap
+association
,同时需要搭配association中的select
属性进行SQL关联,属性的返回值类型使用javaType
进行设置PS:感觉这个方式最为麻烦,不推荐使用
但是它有一个优点:延迟加载,也称作懒加载
懒加载:是指只执行需要获取数据的SQL,当关联多个SQL语句,我们所需的结果只需要通过其中一个SQL就能获取了,使用懒加载就只会执行这一个SQL而不会执行其他SQL,从而大大节约资源,MyBatis默认是关闭懒加载的,需要手动打开
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> settings>
备注:
lazyLoadingEnabled
设置为true,aggressiveLazyLoading
设置为false时,就是按需加载,如果aggressiveLazyLoading
设置为true时,lazyLoadingEnabled
属性会失效分布查询步骤:
1)Step1:直接查询Emp表
2)Step2:使用
ResultMap
+association
引用另一张表的查询如果在核心配置文件使用setting设置了全局格式转换,就可以将id和result标签的映射去掉了,但是在上一种方式却不行
当我们mybatis核心配置文件中的settings标签中进行了全局设置后,如果想要单独设置一个不一样的分布查询SQL,可以使用==
fetchType
==属性:①eager
立即加载(完整加载)②lazy
(延迟加载)假如我们全局设置了延迟加载,想要单独设置一个关联的SQL进行立即加载,就可以将fetchType属性的值设置为eager
3)Step3:查询另一张表Dept
备注:一般而言,不同表的SQL方法写在各表各自对应的Mapper接口中
可以测试,发现同样能成功查询到结果:
开启懒加载后,测试结果:
当
aggresssiveLazyLoading
属性设置为true时,懒加载失效一对多的情况:
也就是反过来,部门表对员工表,一个部门有多个员工
对多需要设置一个集合类型的数据
通过id字段进行查看整行数据
基本上和任务1的代码相同,不同的地方:
SQL映射文件:
备注:
所有的statement都推荐使用#{}
,${}
唯一的优点就是用于设置动态表名或字段名,比较灵活(但同样存在SQL注入)
对于比较特殊的SQL,比如:模糊查询、批量删除、动态设置表名,不能直接使用#{}
,否则会报SQLException
解决方案:
${}
concat
函数:concat('%',#{mohu},'%')
"%"#{mohu}"%"
而批量删除和动态设置表名时,都会因为预编译产生一个被被引号包裹的字符串,而导致SQL异常
批量删除:in('1,2,3,...')
;动态设置表名:'tableName'
,表名不能带引号
parameterType
属性:
特殊字符的处理:
1)使用转义字符,常见转义字符表
符号 | 对应转义符 |
---|---|
> | > |
< | < |
& | & |
2)使用CDATA区
备注:好像测试出现了一个TypeException
异常
Java代码:
多参数传值问题:
使用注解@Param("参数名称")
传值。要求==@Param注解的参数名称要和statement中占位符中的参数名称要一致==
至于为什么要保持一致,可以去看看参数传递这一小节
使用对象传值。将多个参数封装成一个实体对象 ,将该实体对象作为接口的方法参数,要求实体对象的属性名要和statement中占位符中的参数名称要一致
使用Map集合传值。要求Map集合的键(Key)值要和statment中占位符的参数名称要一致,值(Value)表示用来设置的值
备注:不按照要求来并不会报错,而是查找可能为空
相应的测试方法:
/**
* 多条件查询
* @throws Exception
*/
@Test
public void testSelectByCondition() throws Exception {
//模拟用户输入
int status = 1;
String companyName = "华为";//公司名
String brandName = "华为";//品牌名称
/*使用注解传参数,需要处理参数*/
companyName = "%"+companyName+"%";
brandName = "%"+brandName+"%";
/*使用对象传参数*/
//不建议使用构造器,因为构造器存在硬编码问题,不够灵活
Brand brand = new Brand();
brand.setStatus(status);
brand.setCompanyName(companyName);
brand.setBrandName(brandName);
/*使用Map集合传参数*/
Map map = new HashMap();
map.put("status",status);
map.put("companyName",companyName);
map.put("brandName",brandName);
//1、加载MyBatis核心配置文件,获取SqlSessionFactory对象
//1.1 确定Mybatis核心配置文件路径
String resource = "./mybatis-config.xml";
//1.2 从resources目录下读取MyBatis配置文件,获取InputStream对象
InputStream is = Resources.getResourceAsStream(resource);
//1.3 获取SqlSessionFactory对象
SqlSessionFactory sqlSF = new SqlSessionFactoryBuilder().build(is);
//2、获取SqlSession对象
SqlSession sqlS = sqlSF.openSession();
//3、执行SQL语句(使用Mapper代理)
//3.1 获取Mapper接口对象
BrandMapper brandMapper = sqlS.getMapper(BrandMapper.class);
/*----------------核心代码(其他的代码可以进行CV)-----------------*/
//3,2 使用Mapper接口对象执行方法
/*使用注解传参数*/
//List brands = brandMapper.selectByCondition(status, companyName, brandName);
/*使用对象传参数*/
//List brands = brandMapper.selectByCondition(brand);
/*使用Map集合传参数*/
List<Brand> brands = brandMapper.selectByCondition(map);
//输出检测
System.out.println(brands);
/*--------------------------------------------------------------*/
//4、释放资源
sqlS.close();
}
前面的代码仍存在bug!
有时候用户只想查询品牌时,只输入一个brandName会导致查不出来结果!这和实际情况相违背,这就需要使用动态SQL进行解决。MyBatis提供了强大的动态SQL!真好o(^▽^)o
动态SQL:SQL语句会随着用户的输入和外部条件的变化而变化
多条件动态查询就是,用户会输入多个参数进行查询,但是参数个数并不确定
详细介绍看官网
查询结果:
使用上面的动态SQL仍存在bug!(我愿称黑马亮亮老师无敌)
当用户不输入status参数时,由于后面一个If标签有逻辑连接词但前面没有任何表达式,会报错导致查不出数据!
解决方法:
使用
标签(MyBatis官方觉得前面那种解决方法太low了,就提供了第二种,MyBatisYYDS)
标签的作用:若有条件成立,会自动生成where关键字;若条件成立,且where关键字后面为 “AND” 或 “OR”,会自动将它们去除;若没有条件成立,则不会生成where关键字
拓展:trim
标签:
因为
标签只能去除前面的逻辑连接词,当逻辑连接词在表达式的后面时,就无法去除了,这就需要使用trim标签
prefix
:在标签内容之前添加一些内容prefixOverrides
:在标签内容之前去除一些内容suffix
:在标签内容之后添加一些内容stuffixOverrides
:在标签内容之后去除一些内容所以我们可以直接使用prefix
属性给标签内容前添加一个where
关键字,然后使用suffixOverrides
属性去除标签内容后的and
逻辑链接词。当然trim
标签也是可以解决逻辑链接词在前的情况
<select id="getEmpListByMoreTJ" resultType="Emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and">
<if test="empName != '' and empName != null">
emp_name = #{empName} and
if>
<if test="age != '' and age != null">
age = #{age} and
if>
<if test="gender != '' and gender != null">
gender = #{gender}
if>
trim>
select>
使用关键字:
choose(when,otherwise)
,类似于switch语句。单条件动态查询就是,用一个statement语句涵盖所有可能出现的单条件查询,大大减少了代码量的冗余。
备注:
一个when标签的条件一旦成立,就不判断后面的条件了
前面的多条件查询statement代码中只需要【将and全部去掉】,然后加一个判断【当所有输入为空时1!=1
的条件】,也能变成一个单条件查询
一般都不会使用
标签,而是将上面的where
换成
标签,会自动过滤掉没有条件的where
如果既不使用
标签,又不使用
标签,就会报错
相应的statement:
相应测试代码:
/**
* 四、添加数据
* @throws Exception
*/
@Test
public void testAdd() throws Exception {
//模拟用户输入(单条件查询,用户只输入一个参数)
int status = 1;
String companyName = "波导手机";//公司名
String brandName = "波导";//品牌名称
String description = "手机中的战斗机";
int ordered = 100;
/*使用对象传参数*/
//不建议使用构造器,因为构造器存在硬编码问题,不够灵活
//id字段一般无需设置,是数据库中的自增字段
Brand brand = new Brand();
brand.setStatus(status);
brand.setCompanyName(companyName);
brand.setBrandName(brandName);
brand.setDescription(description);
brand.setOrdered(ordered);
//1、加载MyBatis核心配置文件,获取SqlSessionFactory对象
//1.1 确定Mybatis核心配置文件路径
String resource = "./mybatis-config.xml";
//1.2 从resources目录下读取MyBatis配置文件,获取InputStream对象
InputStream is = Resources.getResourceAsStream(resource);
//1.3 获取SqlSessionFactory对象
SqlSessionFactory sqlSF = new SqlSessionFactoryBuilder().build(is);
//2、获取SqlSession对象
SqlSession sqlS = sqlSF.openSession();
//3、执行SQL语句(使用Mapper代理)
//3.1 获取Mapper接口对象
BrandMapper brandMapper = sqlS.getMapper(BrandMapper.class);
/*----------------核心代码(其他的代码可以进行CV)-----------------*/
//3,2 使用Mapper接口对象执行方法
/*使用注解传参数*/
brandMapper.add(brand);
/*--------------------------------------------------------------*/
//4、释放资源
sqlS.close();
}
执行成功后会发现,表中并没有添加数据,原因如下:
备注:查询语句不进行事物提交也能查询到结果,事物提交是能够持久影响数据库
方式一:
方式二:
手动提交后,再次执行可以发现表中的数据已经发生改变了
批量添加需要使用动态SQL中的
标签,关于该标签的详解可以参考:删除多个、参数传递
collection
:它的值表示传递的list对象,不使用@Param注解传递参数,collection属性的值可以写:arg0、collection、list
使用注解传递参数,属性值就使用注解设置的值
separator
:表示每次遍历后,在标签内容后添加的内容(最后一次遍历不会添加)
刚好符合SQL批量插入的语法:values(a1,b1,c1,……),values(a2,b2,c2,……),……values(an,bn,cn,……)
注意:非查询SQL记得手动提交事务或者设置自动提交事务
拓展:
useGeneratedKeys
和keyProperty
获取自增的主键
上面的代码仍存在不足之处,即无法直接获取添加数据后的主键id的值!如果想要获取id的值,需要去数据库中进行查找。
解决方法:
userGeneratedKeys
:该属性只能用于insert语句,用于判断是否获取数据插入成功后的主键值,默认是false(表示不获取数据插入成功后的主键值)keyProperty
:一般是和userGeneratedKeys搭配使用的,用于将获取的主键值绑定到实体类的属性id上,绑定成功后,就能够直接使用get方法获取到id的值原生JDBC获取自增主键:
/** * 使用原生JDBC实现动态获取自增主键 */ @Test public void jdbcGenerateKey(){ try { //1、加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2、获取连接对象 String url = "jdbc:mysql:///ssm?serverTimezone=UTC"; String user = "root"; String pwd = "32345678"; Connection connection = DriverManager.getConnection(url, user, pwd); //3、编写SQL String username = "lisi"; String password = "123"; Integer age = 23; String gender = "男"; String eamil = "[email protected]"; String sql = "insert into tb_user values(null,?,?,?,?,?)"; //4、获取SQL执行对象 PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); //给占位符设置值 ps.setString(1, username); // 为sql语句中第二个问号赋值 ps.setString(2, password); // 为sql语句中第三个问号赋值 ps.setInt(3,age); // 为sql语句中第四个问号赋值 ps.setString(4,gender); ps.setString(5,eamil); //5、执行SQL ps.executeUpdate(); //获取新增的一行 ResultSet rs = ps.getGeneratedKeys(); //让光标指向下一行(也就是新增的一行) rs.next(); int id = rs.getInt(1); System.out.println(id); } catch (Exception e) { throw new RuntimeException(e); } }
更具id一次性修改其他字段的所有数据
相应的statement:
相应测试代码:
/**
* 五、修改
* @throws Exception
*/
@Test
public void testUpdate() throws Exception {
//模拟用户输入(单条件查询,用户只输入一个参数)
int status = 1;
String companyName = "波导手机3";//公司名
String brandName = "波导3";//品牌名称
String description = "手机中的战斗机3";
int ordered = 200;
int id= 5;
/*使用对象传参数*/
Brand brand = new Brand(id,brandName,companyName,ordered,description,status);
//1、加载MyBatis核心配置文件,获取SqlSessionFactory对象
//1.1 确定Mybatis核心配置文件路径
String resource = "./mybatis-config.xml";
//1.2 从resources目录下读取MyBatis配置文件,获取InputStream对象
InputStream is = Resources.getResourceAsStream(resource);
//1.3 获取SqlSessionFactory对象
SqlSessionFactory sqlSF = new SqlSessionFactoryBuilder().build(is);
//2、获取SqlSession对象
SqlSession sqlS = sqlSF.openSession(true);//将默认提交方式改成手动
//3、执行SQL语句(使用Mapper代理)
//3.1 获取Mapper接口对象
BrandMapper brandMapper = sqlS.getMapper(BrandMapper.class);
/*----------------核心代码(其他的代码可以进行CV)-----------------*/
//3,2 使用Mapper接口对象执行方法
brandMapper.update(brand);
/*--------------------------------------------------------------*/
//4、释放资源
sqlS.close();
}
}
动态修改就是,只修改用户输入的多个或一个参数。用户输入什么就修改什么,用户就是上帝~ o( ̄▽ ̄)ブ
注意:动态修改需要使用
标签,因为使用
set
关键字会出现很多问题:
- 当最后一条修改语句的参数为空时,会导致前面语句留下
,
导致语法错误- 当修改语句为空,会留下
set
导致语法错误
相应的statem:
用户输入一个id,通过id删除该行
相应的statement:
用户输入多个id,一次性根据多个id删除所在行的数据
传参问题:
底层原理参观后面章节
@Param
注解传数组:备注:
item
:表示遍历的每一个id
separator
:用于设置 遍历后得到的多个id之间的 分隔符
open
:用于拼接
标签的左字符
close
:用于拼接
标签的右字符
一个好的程序员往往都会追求代码的最简O(∩_∩)O
拓展:原始写法
解决方法使用
${}
但是这个基本上都废除了,还是使用动态SQL标签更香一点
在MyBatis中传递参数时,当抽象方法传递多个参数时,都会生成两个键值对,
arg
键值对和**Param
键值对**
示例:(这里使用前面多条件查询的例子,回看)
当我们在进行多个参数传递时,如果注解不和对应statement中占位符的参数名不一致,会直接报错:
直接报错:
报错原因:(重点!)
在使用MyBatis框架时,当我们在Mapper接口中使用抽象方法一次性传递多个参数时,MyBatis底层会直接将这些参数按照先后顺序,直接封装成Map集合,形成键值对:
Key1)
arg0
:Map集合的键(key),表示第一个参数status
传到SQL映射文件中的参数名Value1)
param1
:Map键arg0
对应的值(value),表示第一个参数status
的值Key2)
arg1
:表示第二个参数companyName
传到SQL映射文件对应statement中的参数名Value2)
param2
:表示第二个参数的值以此类推,每一个
arg
都有一个对应的param
……重点来了:当我们使用
@Param
注解时,其实就是给param
起别名,比如当我们使用这段代码List<Brand> selectByCondition(int status, @Param("companyName")String companyName, @Param("brandName")String brandName);
其实就是将
param1
起个别名叫companyName
,将param2
起个别名叫brandName
。这样做的目的显而易见增强代码的可读性
了解报错原因后,我们就可以知道当我们在不使用注解进行传参数时,可以直接使用:
POJO
传递参数:直接使用,要求实体类的属性名和对应statement中占位符的参数名称一致Map
集合传递参数:直接使用,要求Map集合的键名要和对应statement中占位符的参数名称一致POJO类型和Map集合类型不仅可以用来传递单个参数,而且还能用来传递多个参数,关于传递多个参数的使用,已经在前面已经讲过了,相信只要懂了怎么使用它们传递多个参数,对于传递单个参数也是一看就懂
使用Collection
传递参数
会将参数封装成Map集合,同时生成两个键值对:
Key1)arg0
:用来存Collection
集合的对象名
value1)param
:用来存arg0
对应的值
Key2)collection
:用来存Collection
集合的对象名
Value2)存key2对应的值
collection
和arg0
是等价的,都是用来存Collection
集合的对象名
使用List
传递参数
会将参数封装成Map集合,同时生成两个键值对:
Key1)arg0
:用来存List
集合的对象名
Value1)param
:用来存arg0
对应的值
Key2)collection
:用来存List
集合的对象名
Value2)存Key2相应的值
Key3)list
:用来存List
集合的对象名
Value3)存Key3相应的值
使用Array
传递参数
会将参数封装成Map集合,同时生成两个键值对:
Key1)arg0
:用来存Arrray
集合的对象名
Value1)param
:用来存arg0
对应的值
Key2)array
:用来存Array
集合的对象名
Value2)存Key2相应的值
关于参数传递的总结:
在使用MyBatis进项目开发时,遇到以下两种情况,强烈建议使用@Param
注解给参数起别名(提高代码可读性)
①在传多个参数时,直接传递,可以使用Map集合、注解
②传单个参数使用Collection
、List
、Array
这三种方式进行传递
除了POJO
方式以外,其他参数的传递都是使用Map集合进行的
能用来传递多个参数的方式也同样能用来传递单个参数
说了这么多来点实际的,上道面试题 :
正确答案:
- 第一个占位符中的参数填:user.username
- 第二个占位符中的参数填:user.password
填其他答案,查询结果都为空或者直接报错~
答案解析:
两个条件叠加起来,就是答案了。至于为什么是
键名.属性名
这种写法,暂时还未想到原因,我认为可能这就是一种规范吧,类似于为什么引用对象中的方法,需要对象名.方法名
查询结果:
使用注解比使用配置文件更加简便,主要体现:使用注解无需使用配置文件去实现Mapper接口中的抽象方法。
注意:使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句!
测试:
结果:
其他的SQL语句也是一样的,其实就是通过注解将写在配置文件中的SQL语句移到了Mapper接口中了
写在最后的话:
想起两天前我还不知道MyBatis是啥,还对它有一点恐惧和担心,人可能就是这样的,总是对未知的事物产生恐惧,现在学完这一章节虽说还谈不上能熟练使用,但对MyBatis还有一个大致的了解了O(∩_∩)O
- 本文若对你有一定的帮助,欢迎小伙伴们的点赞、评论✍、收藏⭐
- 一起加油,让我们一步步走向聪明绝顶的道路吧
本章思维导图(方便快速回忆,定位知识点):
推荐阅读:
- 【JavaWeb系列】上一篇文章:一文教你快速上手Maven
- 对JDBC学习的回顾:JDBC学习路线总结
持久层也称数据访问层(Data Access Layer)。其功能主要是负责数据库的访问。简单地说就是实现对数据表的Select(查询)、Insert(插入)、Update(更新)、Delete(删除)等操作,即:将数据保存到数据库的那一层代码。 ↩︎
框架就是一个半成品软件,是一套可重用的、通用的、软件基础代码模型。在框架的基础之上构建软件编写更加高效、规范、通用、可扩展。 ↩︎
解耦就是解除两者之间的关联,从而让代码能够随时更换SQL语句 ↩︎
POJO类就是实体类,一般用来接收来自文件、数据库的数据 ↩︎
硬编码,就是将整个SQL语句当作参数直接传进方法中。硬编码不利于代码后续的更新和维护 ↩︎