Mybatis的使用
1.Mybatis特性
- MyBatis是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架
- MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集
- MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的java对象)映射成数据库中的记录
- MyBatis是一个半自动化的ORM(Object Relation Mapping)框架
2.和其他持久层技术对比
- JDBC
- SQL夹杂在Java代码中耦合度高,导致硬编码内伤
- 维护不易且实际开发需求中SQL有变化,频繁修改的情况多见
- 代码冗长,开发效率低
- Hibernate和JPA
- 操作简单,开发效率高
- 程序中的长难复杂SQL需要绕过框架
- 内部自动生产的SQL,不容易做特殊优化
- 基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难
- 反射操作太多,导致数据库性能下降
- Mybatis
- 轻量级,性能出色
- SQL和Java编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
- 开发效率稍逊于Hibernate,但是完全能够接受
3.MyBatis环境搭建
1.创建一个Maven项目:
2.自己安装mysql
- 创建数据库testdb;
CREATE DATABASE testdb CHARACTER SET utf8;
- 创建user表
DROP TABLE IF EXISTS user;
create table user
(
id int auto_increment not null primary key,
name varchar(30) not null,
age int not null
);
3.构造目录
4.构建文件
第一步在
resources
目录下创建核心配置文件mybatis-config.xml文件- 文件内容:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--导入properties文件-->
<properties resource="application.yaml"/>
<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>
<!--引入映射文件-->
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>
- 第二步在resources下创建文件application.yaml文件。
- 文件内容:
#驱动mysql5.5以上为com.mysql.cj.jdbc.Driver,mysql5.5以下为com.mysql.jdbc.Driver
driver: com.mysql.cj.jdbc.Driver
#url换成自己的url:jdbc:mysql://ip:port/testdb?characterEncoding=utf8
url: jdbc:mysql://172.17.0.2:3306/testdb?characterEncoding=utf8
#用户名,生产环境不建议使用root账户,此处仅做实验
username: root
#密码改为自己的
password: 123456
- 第三步:打开pom.xml引入mysql-connector-java、myBatis以及lombok依赖:
- 依赖内容:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<!--引入myBatis依赖-->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<!--引入mysql-connector-java-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
- 第四步:在pojo目录下创建User类:
- User类的结构为:
package org.example.pojo;
import lombok.*;
/**
* @author mr
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private int age;
}
- 第五步:在org.example.mapper目录下为User类创建UserMapper接口
- 接口UserMapper:
package org.example.mapper;
/**
* @author mr
*/
public interface UserMapper {
/**
* MyBatis面向接口编程的两个一致:
* 1、映射文件的namespace要和mapper接口的全类名保持一致
* 2、映射文件中SQL语句的id要和mapper接口中的方法名保持一致
*
* 表--实体类--mapper--映射文件
* */
int insertUser();
}
- 第六步:在resource/mappers下为该UserMapper接口创建映射文件
- UserMapper.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org/DTD Mapper 3.0"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mapper.UserMapper">
<!--int insertUser();-->
<insert id="insertUser">
insert into user values(null ,"cxj",23);
</insert>
</mapper>
对应关系图:
- 第七步:在test/org/example/AppTest.java里添加测试方法:testMyBatis
package org.example;
import static org.junit.Assert.assertTrue;
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.example.mapper.UserMapper;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
/**
* Unit test for simple App.
*/
public class AppTest
{
/**
* Rigorous Test :-)
*/
@Test
public void shouldAnswerWithTrue()
{
assertTrue( true );
}
@Test
public void testMyBatis() throws IOException {
//加载核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//获取SqlSessionFactoryBuilder
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取mapper接口对象,底层是代理模式
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.insertUser();
System.out.println("result:"+i);
}
}
第八步:运行测试方法–>testMyBatis(),得到运行结果:
- 第九步:查看数据库数据进行验证,发现数据为空。
- 主要原因是我们的事务提交在配置中配置的事务管理类型是JDBC–>需要手动提交。
第十步:事务提交,再次执行,然后查询,发现数据库user
表有新增记录。
此处是9,不是一,因为我们执行SQL而不提交事务的情况下它的id是会被占用的。 - 第十一步:开启事务自动提交功能
- myBatis默认手动提交事务
- myBatis默认手动提交事务
- 第十二步:配置myBatis日志
- pom.xml加入log4J依赖
- 在resources下创建文件log4j.xml
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
日志的级别:
FATAL(致命)>ERROR(错误)>WRAN(警告)>INFO(信息)>DEBUG(调试)
从左到右打印的内容越来越细
log4j.xml内容:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />
</layout>
</appender>
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.apache.ibatis">
<level value="info" />
</logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>
运行日志输出:
- 第十三步
- 关于映射文件如何获取传入的参数问题。
首先对接口函数进行改造:
其次:映射文件的插入SQL对应修改:
声明parameterType="org.example.pojo.User"
即参数类型为pojo的User类型,然后采用#{属性名}
的方式获取对象的属性值对应填充。
测试:
结果:
- 关于映射文件如何获取传入的参数问题。
- 第十四步:其它的传参方法。
多参数传值
:
UserMapper
接口里添加User selectUser(int id,String name,int age);
抽象方法。
在UserMapper.xml
里添加对应的mapper映射:
<select id="selectUser" resultType="org.example.pojo.User">
select * from user where id=#{param1} and name=#{param2} and age=#{param3}
</select>
测试:目前mysql数据库中存在的记录。
测试代码:
测试结果:
注:对于多值的传入,大家需要明白的一点就是,mybatis都会将传入的数据封装成map对象,通过key来得到值。也就是说此处mybatis将三个参数值封装成Map.Entry(param1,id),Map.Entry(param2,name)和Map.Entry(param3,age),多值输入底层其实是Map传参。
Map传值
,在映射文件中通过#{key}
来获取对应的值。
在UserMapper
里新增int updateUser(HashMap<String,Object> map);
抽象方法。
为抽象方法int updateUser(HashMap<String,Object> map);
编写对应的mapper->SQL。
演示完多参数传递了,将User selectUser(int id,String name,int age);
改为User selectUser(int id);
我相信难不倒大家~
测试代码:
测试结果:
- 传值方式有
多参传值
、Map传值
、对象传值
、注解传值
。- 注解传值:在
UserMapper
接口里添加int deleteUser(@Param("id") int id,@Param("name") String name,@Param("age") int age);
抽象方法。
在UserMapper.xml中添加对应的mapper->SQL:
测试代码:
测试结果:
四种方式对比图:
通过对比可以得知,四种传参方式,除了对象传参
需要在UserMapper.xml
特别声明参数类型parameterType="org.example.pojo.User"
外,其余均一致。四种传参方式的共同点是均采用#{属性TAG}
获取参数值。
- 注解传值:在
- 补充说明:查询所有数据。
- 查询符合条件的所有数据。
测试代码:
测试结果:
注:查询功能必须设置resultType或resultMap,resultType:设置默认的映射关系,resultMap设置自定义的映射关系。
- 查询符合条件的所有数据。
4.配置文件详解
mybatis-config.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--导入properties文件-->
<properties resource="application.yaml"/>
<environments default="development">
<!--environment:配置某个具体的环境
属性:
id:表示连接数据库的环境的唯一标识,不能重复。
-->
<environment id="development">
<!--transactionManager:设置事务的管理方式
属性:
type="JDBC|MANAGED"
JDBC:表示当前环境中执行SQL时,使用的是JDBC中原生的事务管理方式
MANAGED:表示被管理,例如Spring
-->
<transactionManager type="JDBC"/>
<!--dataSource:配置数据源
属性:
type:设置数据源的类型
type="POOLED|UNPOOLED|JNDI"
POOLED:表示数据库连接池缓存数据库连接
UNPOOLED:表示不使用数据库连接池
JNDI:表示使用上下文中的数据源
-->
<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>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>
- 关于类型别名:
- 在
myatis-config.xml
中起了类型别名之后,就可以在UserMapper
等mapper
文件中使用别名代替全类名。 - 最常用的设置别名的方式,以包为单位批量设置默认别名即类名的大小写形式作为别名,关键字
package
。
关于mappers
标签,
注意:在resources
下创建包的时候,如果我们采用cn.edu.xaut.mybatis.xml
创建,创建出来的结果是一个目录,而不是分层的多级目录,在resources下创建包的时候,我们采用cn/edu/xaut/mybatis/xml
创建的包才是多层的分级目录。
图示:
以包为单位引入mapper.xml
映射文件,需要注意的两点。
- 在
- 首先为什么需要目录结构一致呢,如下图可知MyBatis在编译时可以将
java
和resources
相同目录结构的文件放到一起,而mapper接口名称
为什么要和它的映射文件名称
保持一致呢,其实主要是为了能在调用根据mapper接口
生成的动态代理对象的某个SQL方法
时,能根据mapper接口名.xml
找到对应的映射文件并根据id
定位到方法。
5.MyBatis中获取参数值的两种方式(重点)
- MyBatis获取参数值的两种方式:
${}
和#{}
。 ${}
的本质就是字符串拼接,#{}
的本质就是占位符赋值。${}
使用字符串拼接
的方式拼接sql,若为字符串类型或日期类型
的字段进行赋值时,需要手动加单引号;但是#{}
使用占位符赋值
的方式拼接sql,此时为字符串类型或日期类型
的字段进行赋值时,可以自动添加单引号。${}
容易引起SQL注入
,所以平时最常用的是占位符赋值#{}
。
- MyBatis获取参数值的各种情况:
- 1.mapper接口方法的参数为单个的字面量类型
- 可以通过
${}
和#{}
以任意的名称获得参数值,但是需要注意${}
的单引号问题。 - 虽然可以通过任意的名称获得参数值,但是建议用参数值的名称来获取,做到见名知意,此处可以通过任意的名称获取参数值。
- 可以通过
- 1.mapper接口方法的参数为单个的字面量类型
- 2.对于多个参数的传送,MyBatis将会把所有的参数封装到Map对象中,键值呢,分别为
arg0:参数1
,arg1:参数2
,param1:参数1
,param2:参数2
,我们在执行对应的SQL
的时候只需要通过#{arg0}/#{param1}
获取参数1并进行对应填充复制,以及通过#{arg1}/#{param2}
获取参数2等依次类推并进行填赋值或者字符串拼接${arg0}/${param1}
获取参数值并进行拼接(当然由于字符串拼接会导致SQL注入
,因此并不建议采用)。
- 3.自封装
Map<String,Object>
就很好理解了,话不多说,如下图。- 注意点:1.
'${}'
和#{}
可以混用即可以在同一个SQL
语句中出现(本人亲测)。 - 注意点:2.用
${}
时需要注意,对于字符值我们需要加''
用于字符串的拼接,而对于int
类型的数据,我们加单引号'${}'
或者不加单引号${}
,MyBatis均能进行正确解析(本人亲测)。
- 注意点:1.
- 4.参数为实体类对象(常用)
- 只需要通过
#{}
和${}
以属性的方式访问属性值即可,但是需要注意'${}'
的''
问题。
另外,附上'${}'
和#{}
可以混用的证据。
- 只需要通过
- 5.命名参数(常用)
- 使用
@Param
注解命名参数,此时MyBatis会将这些参数放在一个Map集合
中,以两种方式进行存储。- 以
@Param注解的值
为键,以参数为值。 - 以
param1
、param2
为键,以参数为值。 - 因此只需要通过
#{}
和'${}'
以键的方式访问值即可,但是需要注意${}
的单引号''
问题,且${}
方式容易引起SQL注入
。
- 以
- 使用
- 底层
Map
放了两套key:value
的地方(一个是param1:value
,另一个是id:value
,此处id是@Param("id")
的id)。
6.MyBatis的各种查询功能
- 1、若查询出的数据只有一条
- 可以通过实体类接收对象
- 可以通过集合来接收对象
- 可以通过Map来接收对象
通过Map接收的结果:
- 2、若查询出的数据有多条,一定不能通过实体对象来接收,此时会抛出TooManyResultsException。
- 可以通过list集合接收
- 可以通过Map类型的集合来接收
- 通过集合来接收的两种方式:
- 通过集合来接收的两种方式:
- 直接通过Map集合来接收,需要在mapper接口的方法上添加
@MapKey("key")
- Map集合的结果如下:
- 3、查询某个表中的总记录数。
- 注意:当我们在选择
count(*)
、count(1)
、count(字段)
时注意区别。count(*)
与count(1)
:- 使用
count函数
,当要统计的数量比较大
时,发现count(*)
花费的时间比较多,相对来说count(1)
花费的时间比较少。 - 如果你的数据表
没有主键
,那么count(1)比count(*)
快;如果有主键的话,那主键(联合主键)作为count的条件也比count(*)
要快 。 - 如果你的表
只有一个字段的话那count(*)
就是最快
的。 - 如果
count(1)是聚索引,id
,那肯定是count(1)快
,但是差的很小的。因为count(*),自动会优化指定到那一个字段
。所以没必要去count(1),用count(*),sql会帮你完成优化
。此时count(1)和count(*)基本没有区别!
- 使用
count(*)
与count(列名)
:- 在
数据记录都不为空
的时候查询出来结果上没有差别
的; count(*)
(是针对全表)将返回表格中所有存在的行的总数包括值为null的行
;count(列名)
(是针对某一列)将返回表格中某一列除去null以外的所有行
的总数。
- 在
引申:
distinct 列名
,得到的结果将是除去值为null和重复数据后的结果。
- 4、MyBatis默认的类型别名
Alias | Mapping Type |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
5.MyBatis特殊SQL的执行
- 1.模糊查询
模糊查询的SQL
中#{}
和${}
用法。
- 1.模糊查询
2.批量删除
- 批量删除,采用SQL语句的
in
关键字。
- 实验结果图:
- 批量删除,采用SQL语句的
3.动态设置表名