MyBatis最初是Apache的一个开源项目iBatis,2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下,iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github。
iBatis一词来源于"internet"和"abatis"的组合,是一个基于Java的持久层框架。iBatis提供的持久层框架包括SQLMaps和Data Access Obiects(DAO)。
MyBatis官网下载地址:https://github.com/mybatis/mybatis-3
打开链接之后往下拉,找到这个地方
点进去之后,下载jar包(主体)
下载完之后,其中有mybatis的jar包已经官方文档pdf(方便学习,后续会使用到)。
或者,如果嫌上面下载的方式太麻烦,则可以采用maven依赖项式的下载,将下面代码导入依赖配置中即可。
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.13version>
dependency>
JDBC
硬编码
(将代码写死)内伤Hibernate和JPA
MyBatis
首先创建一个空项目,然后再下面创建一个maven工程
注意,这里的打包方式要是jar方式。
下面将引入依赖(pom.xml)
<dependencies>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.13version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
<scope>testscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.32version>
dependency>
dependencies>
依赖引入完成后,则需要配置一下MyBatis的核心配置文件
在main包下的resources路径下创建一个mybatisConfig.xml文件,然后进行配置。
这时就需要用到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.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/MyBatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
mappers>
configuration>
这里用到了mysql中的账号和密码需要改成自己mysql上对应的账号和密码。
该接口相当于以前的DAO,但是mapper仅仅是一个接口,不需要提供实现类。
当然,在此之前,需要在mysql中创建上述配置中的数据库,并且创建一张user表,创建代码如下。
CREATE DATABASE MyBatis;
USE Mybatis;
CREATE TABLE `t_user`(
`id` INT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(20),
`password` VARCHAR(20),
`age` INT, `sex` CHAR,
`email` VARCHAR(20), PRIMARY KEY (`id`)
)ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci;
接下来,创建User的实体类(POJO)
package code;
/**
* @创建人 HeXin
* @所属包 User
* @所属项目 MyBatis
* @创建时间 2023/3/30 20:38
* @描述 User实体类
*/
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
private String sex;
private String email;
public User () {
}
public User (Integer id, String username, String password, Integer age, String sex, String email) {
this.id = id;
this.username = username;
this.password = password;
this.age = age;
this.sex = sex;
this.email = email;
}
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 Integer getAge () {
return age;
}
public void setAge (Integer age) {
this.age = age;
}
public String getSex () {
return sex;
}
public void setSex (String sex) {
this.sex = sex;
}
public String getEmail () {
return email;
}
public void setEmail (String email) {
this.email = email;
}
@Override
public String toString () {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", email='" + email + '\'' +
'}';
}
}
创建User的mapper接口,在里面定义了一个添加用户信息的方法
package code.mapper;
/**
* @创建人 HeXin
* @所属包 UserMapper
* @所属项目 MyBatis
* @创建时间 2023/3/30 20:40
* @描述 User类的Mapper接口
*/
public interface UserMapper {
/**
* @Description: 添加用户名信息
* @CreateTime: 2023/3/30 21:41
* @Author: HeXin
*/
int addUser();
}
这里要引入一个概念,对象关系映射(ORM:Object Relationship Mapping)
其中的对象是指的实体类对象,关系是关系型数据库,映射为二者之间的对应关系。
Java概念 | 数据库概念 |
---|---|
类 | 表 |
属性 | 列/字段 |
对象 | 记录/行 |
在main中的resources中创建一个mappers软件包,并在此创建一个UserMapper.xml配置文件。
查询官方文档,找到关于映射文件的配置部分,将其拷贝过来,并在其中实现插入一条用户信息的sql。
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="code.mapper.UserMapper">
<insert id="addUser">
insert into t_user values(null,"xiaoming","123456",19,'男',"[email protected]");
insert>
mapper>
创建映射文件的注意事项(重要
):
MyBatis映射文件用于编写SQL,访问以及操作表中的数据。MyBatis映射文件存放的位置为src/main/resources/mappers目录下。
在test包下创建TestMyBatis测试类,并实现测试方法
@Test
public void testMyBatis(){
InputStream stream = null;
try {
//加载核心配置文件
stream = Resources.getResourceAsStream("mybatisConfig.xml");
} catch (IOException e) {
e.printStackTrace();
}
//获取SqlSessionFactoryBuilder
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//获取sqlSessionFactory
SqlSessionFactory build = builder.build(stream);
//获取SqlSession
SqlSession sqlSession = build.openSession();
//获取mapper接口对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//测试功能
int i = mapper.addUser();
//提交事务
sqlSession.commit();
System.out.println("受影响行数:"+i);
}
将代码中的一些参数进行解读
其中存在着工厂模式(如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的相关代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”我们需要的对象)。
注意:当测试功能实现后一定要进行事务提交,否则数据库那边会没有数据显示,因为SqlSessionFactory的openSession方法中有一个autoCommit参数,默认为false,也就是默认不自动提交事务。
若需要自动提交事务,可将其参数值改为true(此时自己写的提交事务就可以注释掉了),当需要手动进行提交事务的时候,直接使用默认情况即可。
//获取SqlSession
SqlSession sqlSession = build.openSession(true);
首先要加入相对应依赖
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
创建一个名为log4j.xml的配置文件,其存放在main下的resources目录下
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>
日记级别
FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试)
从左到右打印的内容越来越详细
这里了解一下即可,现在使用时将其拷贝过去即可,以后在ssm整合的环境中是可以没有核心配置文件的,因为核心配置文件中的所有内容都可以交给spring处理。
DOCTYPE configuration
PUBLIC "-//MyBatis.org//DTD Config 3.0//EN"
"http://MyBatis.org/dtd/MyBatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties">properties>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
settings>
<typeAliases>
<package name="code"/>
typeAliases>
<environments default="mysql_test">
<environment id="mysql_test">
<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="code.mappers"/>
mappers>
configuration>
到此,目前需要用到的核心配置文件已经配置完成,下面将开始MyBatis的CRUD功能(增删改查)
添加
<insert id="addUser">
insert into t_user values(null,"xiaohong","123456",19,'女',"[email protected]");
insert>
删除
<delete id="deleteUser">
delete from t_user where id=4;
delete>
修改
<update id="updateUser">
update t_user set username = "lihua",sex = '男' where id=6;
update>
查询
<select id="getUserById" resultType="User">
select * from t_user where id=5;
select>
<select id="getUsers" resultType="User">
select * from t_user;
select>
@Test
public void testAdd(){
InputStream stream = null;
try {
//加载核心配置文件
stream = Resources.getResourceAsStream("mybatisConfig.xml");
} catch (IOException e) {
e.printStackTrace();
}
//获取SqlSessionFactoryBuilder
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//获取sqlSessionFactory
SqlSessionFactory build = builder.build(stream);
//获取SqlSession
SqlSession sqlSession = build.openSession(true);
//获取mapper接口对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//测试功能
int i = mapper.addUser();
System.out.println("受影响行数:"+i);
}
@Test
public void testUpdate(){
InputStream stream = null;
try {
stream = Resources.getResourceAsStream("mybatisConfig.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream);
SqlSession sqlSession = build.openSession(true);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.updateUser();
}
@Test
public void testDelete(){
InputStream stream = null;
try {
stream = Resources.getResourceAsStream("mybatisConfig.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream);
SqlSession sqlSession = build.openSession(true);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.deleteUser();
}
@Test
public void testSelect(){
InputStream stream = null;
try {
stream = Resources.getResourceAsStream("mybatisConfig.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream);
SqlSession sqlSession = build.openSession(true);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//根据id查询用户信息
System.out.println(mapper.getUserById());
System.out.println("--------------------------------");
//查询所有用户信息
List<User> users = mapper.getUsers();
users.forEach(user -> System.out.println(user));
}
到此,MyBatis最基础的框架与功能已经实现~
MyBatis获取参数值有两种方式,一种是${},一种是#{}
${}的本质是使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,则需要手动
加单引号。
#{}的本质是占位符赋值,此时为字符串类型或日期类型的字段进行赋值时,可以自动
添加单引号。
此时,新建一个模板,重新搭建一个MyBatis框架。因为其核心配置文件每次都去官方文档拷贝实在太麻烦,所以这里使用IDEA中的代码模板功能,后续需要核心配置文件和映射文件时直接可以一键生成。
在idea中配置核心配置文件模板
第一步
第二步
将简单的模板代码拷贝进模板文件主体中
DOCTYPE configuration
PUBLIC "-//MyBatis.org//DTD Config 3.0//EN"
"http://MyBatis.org/dtd/MyBatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties">properties>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
settings>
<typeAliases>
<package name=""/>
typeAliases>
<environments default="mysql_test">
<environment id="mysql_test">
<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=""/>
mappers>
configuration>
在idea中配置映射文件模版
第一步与配置核心文件时一样,区别在于第二步
第二步
若mapper接口中的方法参数为单个的字面量类型 此时可以使用${}和#{}以任意的名称获取参数的值,注意${}需要手动加单引号。
首先,因为每次测试时都需要创建的sqlsession,存在大量一样的代码,所以将其提取出来形成一个工具类,叫做SqlSessionUtils
package utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
/**
* @创建人 HeXin
* @所属包 SqlSessionUtils
* @所属项目 MyBatis
* @创建时间 2023/4/1 10:08
* @描述 获取SqlSession的工具类
*/
public class SqlSessionUtils {
public static SqlSession getSqlSession(){
InputStream stream = null;
try {
stream = Resources.getResourceAsStream("mybatisConfig.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream);
SqlSession sqlSession = build.openSession(true);
return sqlSession;
}
}
下面将用代码实现这两种方式获取参数
#{}
查询代码
<select id="getUserByUsername" resultType="User">
select * from t_user where username = #{username};
select>
测试代码
@Test
public void testSingle(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserByUsername("xiaohong");
System.out.println(user);
}
测试的结果:
从测试的结果可以看出,#{}的确使用的?占位符赋值获取参数
${}
查询代码
<select id="getUserByUsername" resultType="User">
select * from t_user where username = '${username}';
select>
测试代码
@Test
public void testSingle(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserByUsername("xiaohong");
System.out.println(user);
}
测试的结果:
从测试结果可以看出,${}使用的是字符串拼接的方式进行赋值
值得注意的时,因为${}不会自动添加单引号,所以当使用${}时,一定要记得用单引号将其进行包围,否则就会报出一下异常
若mapper接口中的方法参数为多个时 此时MyBatis会自动将这些参数放在一个map集合中,以arg0,arg1…为键,以参数为值;以 param1,param2…为键,以参数为值;因此只需要通过${}和#{}访问map集合的键就可以获取相对应的值。由于上面获取单个参数已经演示过${}和#{}的使用和区别,下面就演示其中一种。
代码演示
查询代码(错误方式)
<select id="login" resultType="User">
select * from t_user where username = #{username} and password = #{password};
select>
测试代码
@Test
public void testLogin(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.login("xiaohong", "123456");
System.out.println(user);
}
当有多个参数时,不能像单个参数一样直接使用形参名获取,否则会报异常
上面的错误信息中不仅告诉了哪里出错,还给出了解决方案。
查询代码(正确方式)
<select id="login" resultType="User">
select * from t_user where username = #{arg0} and password = #{arg1};
select>
测试结果
若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,将这些数据放在map中 只需要通过${}或#{}访问map集合的键就可以获取相对应的值。此方式由获取多参数方式演变而来,相当于自己设置键来访问数据。
代码实现
查询代码
<select id="login" resultType="User">
select * from t_user where username = #{username} and password = #{password};
select>
测试代码
@Test
public void testLogin(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("username","xiaohong");
map.put("password","123456");
User user = mapper.login(map);
System.out.println(user);
}
测试结果与获取多参数的结果一样
若mapper接口中的方法参数为实体类对象时
此时可以使用${}和#{},通过访问实体类对象中的属性名获取属性值。
代码演示
向表中插入数据
<insert id="addUser">
insert intot_user values(null,#{username},#{password},#{age},#{sex},#{email})
insert>
测试代码
@Test
public void testAdd(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.addUser(new User(null, "xiaoming", "123456", 19, "男", "[email protected]"));
System.out.println(i);
}
测试结果
此方式可以将第一种(获取单个参数)、第二种(获取多个参数)、第三种(获取map类型)方式整合成一种方式,都可以使用@Param进行实现获取参数。
此时,会将这些参数放在map集合中,以@Param注解的value属性值为键,以参数为值;以param1,param2…为键,以参数为值;只需要通过${}和#{}访问map集合的键就可以获取相对应的值。
代码演示
查询代码
<select id="login" resultType="User">
select * from t_user where username = #{username} and password = #{password};
select>
测试代码
@Test
public void testLogin(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.login("xiaohong", "123456");
System.out.println(user);
}
测试结果
共分为五种查询情况:查询单个实体类对象、查询一个list集合、查询单个数据、查询一条数据为map集合,查询多条数据为map集合。下面将逐个进行学习
这种查询情况已经实现过很多次了,这里就不多赘述,直接上代码
/**
* @Description:根据id查询用户信息
* @CreateTime: 2023/4/1 12:00
* @Author: HeXin
*/
User getUserById(@Param("id") Integer id);
查询语句
<select id="getUserById" resultType="User">
select * from t_user where id = #{id};
select>
测试代码
@Test
public void testGetUser(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
User user = mapper.getUserById(5);
System.out.println(user);
}
测试结果
这种情况可以包含第一种情况,因为list集合中可以不包含任何数据,也可以只包含一条数据。
/**
* @Description: 查询所有用户信息
* @CreateTime: 2023/4/1 12:10
* @Author: HeXin
*/
List<User> getUsers();
查询语句
<select id="getUsers" resultType="User">
select * from t_user;
select>
测试代码
@Test
public void testGetUsers(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
List<User> users = mapper.getUsers();
users.forEach(user -> System.out.println(user));
}
测试结果
单个数据,一般是指单行单列的数据,如一张表上的总记录数、统计总共的金额等等。
下面将以查询用户信息的总记录数为例进行学习
代码演示
/**
* @Description: 查询用户的总记录数
* @CreateTime: 2023/4/1 14:48
* @Author: HeXin
*/
Integer getCount();
查询语句(MyBatis设置的有默认的类型别名,所以这里的resultType设置为全类名或别名都行。)
<select id="getCount" resultType="Integer">
select count(*) from t_user;
select>
测试代码
@Test
public void testGetCount(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
Integer count = mapper.getCount();
System.out.println("总记录数:"+count);
}
测试结果为
通过查找官方文档,我们可以得知,MyBatis设置的默认全类名有以下这些
就是将查询到的一条数据存储到Map集合中。
代码演示
/**
* @Description: 根据id查询用户信息为一个map集合
* @CreateTime: 2023/4/1 15:07
* @Author: HeXin
*/
Map<String,Object> getUserByIdToMap(@Param("id") Integer id);
查询语句
<select id="getUserByIdToMap" resultType="map">
select * from t_user where id = #{id};
select>
测试代码
@Test
public void testGetUserToMap(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
Map<String, Object> userMap = mapper.getUserByIdToMap(6);
System.out.println(userMap);
}
测试结果
将查询到的多个数据都存储在Map集合中。这种方式以后的使用频率是很高的,因为map可以转换成json对象然后将数据传递给前端。注意,这里的多条数据不能只用一个Map来接收,应该用一个Map的LIst集合来接收(或者用@MapKey设置一个键(Key),此时就可以将每条数据转换的Map集合作为值,以某个字段的值作为主键,放在同一个Map集合中),否则就会报异常(此异常与用一个参数接收多条数据时的异常一样)
代码演示
/**
* @Description: 查询所有用户信息为Map集合
* @CreateTime: 2023/4/1 15:18
* @Author: HeXin
*/
List<Map<String,Object>> getUsersToMap();
查询语句
<select id="getUsersToMap" resultType="map">
select * from t_user;
select>
测试代码
@Test
public void testGetUsersToMap() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
List<Map<String, Object>> users = mapper.getUsersToMap();
users.forEach(user-> System.out.println(user));
}
测试结果
/**
* @Description: 查询所有用户信息为Map集合
* @CreateTime: 2023/4/1 15:18
* @Author: HeXin
*/
@MapKey("id")
Map<Integer,Object> getUsersToMap();
查询语句
<select id="getUsersToMap" resultType="map">
select * from t_user;
select>
测试代码
@Test
public void testGetUsersToMap() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
Map<Integer, Object> users = mapper.getUsersToMap();
users.forEach((id,user)-> System.out.println("key="+id+" "+user));
}
测试结果
到此,MyBatis中的各种查询功能已经学习完毕。
这里就存在一个问题,因为#{}是占位符赋值,所以会将sql语句中的单引号部分内容替换成问号,并且会自动加上单引号,导致查询失败,所以这个时候有三种解决办法,下面进行学习
以根据用户名模糊查询为例
/**
* @Description: 根据用户名进行模糊查询
* @CreateTime: 2023/4/1 15:52
* @Author: HeXin
*/
List<User> getUserByLike(@Param("username")String username);
<select id="getUserByLike" resultType="User">
select * from t_user where username like '%${username}%'
select>
<select id="getUserByLike" resultType="User">
select * from t_user where username like concat('%',#{username},'%')
select>
<select id="getUserByLike" resultType="User">
select * from t_user where username like "%"#{username}"%"
select>
测试代码
@Test
public void testGetUserByLike(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);
List<User> user = mapper.getUserByLike("xiao");
user.forEach(u-> System.out.println(u));
}
测试结果
/**
* @Description: 批量删除
* @CreateTime: 2023/4/1 16:11
* @Author: HeXin
*/
int deleteMore(@Param("ids") String ids);
SQL语句
<delete id="deleteMore">
delete from t_user where id in(${ids})
delete>
测试代码
@Test
public void testDeleteMore(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);
int i = mapper.deleteMore("5,6,7");
System.out.println("受影响行数:"+i);
}
测试结果
根据表名来查询数据,当然,这里也应该使用${}来获取参数
/**
* @Description: 查询指定表中的数据
* @CreateTime: 2023/4/1 16:29
* @Author: HeXin
*/
List<User> getUserByTableName(@Param("tableName") String tableName);
查询语句
<select id="getUserByTableName" resultType="User">
select * from ${tableName};
select>
测试代码
@Test
public void testGetUserByTableName(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);
List<User> users = mapper.getUserByTableName("t_user");
users.forEach(user-> System.out.println(user));
}
测试结果
这是没有添加获取自增主键的添加用户信息打印结果,很显然,其id值为null
因为增删改有统一的返回值是受影响的行数,
因此只能将获取的自增的主键放在传输的参数user对象的某个属性中。
/**
* @Description: 添加用户信息
* @CreateTime: 2023/4/1 17:14
* @Author: HeXin
*/
void addUser(User user);
添加语句
<insert id="addUser" useGeneratedKeys="true" keyProperty="id">
insert into t_user values(null,#{username},#{password},#{age},#{sex},#{email});
insert>
测试代码
@Test
public void testAddUser(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);
User user = new User(null,"WLM","1122334",18,"女","[email protected]");
mapper.addUser(user);
System.out.println(user);
}
测试结果
这次添加,其id值就不是null了。
resultMap:设置自定义映射
这里将会用到多对一或一对多的关系,所以需要再创建两张表。
CREATE TABLE `t_emp`(
`eid` INT NOT NULL AUTO_INCREMENT,
`emp_name` VARCHAR(20),
`age` INT,
`sex` CHAR,
`email` VARCHAR(20),
`did` INT,
PRIMARY KEY (`eid`)
) ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci;
CREATE TABLE `t_dept`(
`did` INT NOT NULL AUTO_INCREMENT,
`dept_name` VARCHAR(20),
PRIMARY KEY (`did`)
) ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci;
向表中插入数据
t_emp
INSERT INTO `mybatis`.`t_emp` (`eid`, `emp_name`, `age`, `sex`, `email`, `did`)
VALUES ('1', 'xiaoming', '18', '男', '[email protected]', '1');
INSERT INTO `mybatis`.`t_emp` (`eid`, `emp_name`, `age`, `sex`, `email`, `did`)
VALUES ('2', 'xiaohong', '19', '女', '[email protected]', '1');
INSERT INTO `mybatis`.`t_emp` (`eid`, `emp_name`, `age`, `sex`, `email`, `did`)
VALUES ('3', 'lihua', '22', '男', '[email protected]', '2');
INSERT INTO `mybatis`.`t_emp` (`eid`, `emp_name`, `age`, `sex`, `email`, `did`)
VALUES ('4', 'wangqiang', '21', '男', '[email protected]', '3');
t_dept
INSERT INTO `mybatis`.`t_dept` (`did`, `dept_name`) VALUES ('1', 'A');
INSERT INTO `mybatis`.`t_dept` (`did`, `dept_name`) VALUES ('2', 'B');
INSERT INTO `mybatis`.`t_dept` (`did`, `dept_name`) VALUES ('3', 'C');
对应的,创建两个表的实体类
package POJO;
/**
* @创建人 HeXin
* @所属包 Emp
* @所属项目 MyBatis
* @创建时间 2023/4/1 18:00
* @描述
*/
public class Emp {
private Integer eid;
private String empName;
private Integer age;
private String sex;
private String email;
private Dept dept;
public Dept getDept () {
return dept;
}
public void setDept (Dept dept) {
this.dept = dept;
}
public Emp (Integer eid, String empName, Integer age, String sex, String email) {
this.eid = eid;
this.empName = empName;
this.age = age;
this.sex = sex;
this.email = email;
}
public Emp () {
}
public Integer getEid () {
return eid;
}
public void setEid (Integer eid) {
this.eid = eid;
}
public String getEmpName () {
return empName;
}
public void setEmpName (String empName) {
this.empName = empName;
}
public Integer getAge () {
return age;
}
public void setAge (Integer age) {
this.age = age;
}
public String getSex () {
return sex;
}
public void setSex (String sex) {
this.sex = sex;
}
public String getEmail () {
return email;
}
public void setEmail (String email) {
this.email = email;
}
@Override
public String toString () {
return "Emp{" +
"eid=" + eid +
", empName='" + empName + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", email='" + email + '\'' +
", dept=" + dept +
'}';
}
}
package POJO;
import java.util.List;
/**
* @创建人 HeXin
* @所属包 Dept
* @所属项目 MyBatis
* @创建时间 2023/4/1 18:02
* @描述
*/
public class Dept {
private Integer did;
private String deptName;
private List<Emp> emps;
public List<Emp> getEmps () {
return emps;
}
public void setEmps (List<Emp> emps) {
this.emps = emps;
}
public Dept (Integer did, String deptName) {
this.did = did;
this.deptName = deptName;
}
public Dept () {
}
public Integer getDid () {
return did;
}
public void setDid (Integer did) {
this.did = did;
}
public String getDeptName () {
return deptName;
}
public void setDeptName (String deptName) {
this.deptName = deptName;
}
@Override
public String toString () {
return "Dept{" +
"did=" + did +
", deptName='" + deptName + '\'' +
", emps=" + emps +
'}';
}
}
若字段名与实体类中的属性名不一致,则通过查询语句查询数据的时候,是无法查到的,这是因为字段名有自己的风格要求(下划线),属性名也有风格要求(驼峰)。解决此问题共有三个办法:
给查询的时候给字段名起别名,将别名设置为驼峰风格的。(实现起来比较麻烦,尤其是当字段名很多的时候,非常消耗时间)。
通过设置MyBatis核心配置文件,设置全局变量,将下划线自动映射为驼峰。该配置已经在完善核心配置文件时已经涉及到了。
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
通过resultMap设置自定义的映射关系
<resultMap id="userMap" type="User">
<id property="id" column="id">id>
<result property="userName" column="user_name">result>
<result property="password" column="password">result>
<result property="age" column="age">result>
<result property="sex" column="sex">result>
resultMap>
<select id="getEmps" resultMap="userMap">
select * from t_emp;
select>
一共有三种处理方式。通过查询员工信息及其对应部门信息为例。
/**
* @Description: 查询员工及其对应部门信息
* @CreateTime: 2023/4/2 10:33
* @Author: HeXin
*/
Emp getEmpAndDept(@Param("eid") Integer eid);
查询语句
<resultMap id="empAndDeptResultMap" type="Emp">
<id property="eid" column="eid">id>
<result property="empName" column="emp_name">result>
<result property="age" column="age">result>
<result property="sex" column="sex">result>
<result property="email" column="email">result>
<result property="dept.did" column="did">result>
<result property="dept.deptName" column="dept_name">result>
resultMap>
<select id="getEmpAndDept" resultMap="empAndDeptResultMap">
select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid};
select>
查询语句
<resultMap id="empAndDeptResultMap" type="Emp">
<id property="eid" column="eid">id>
<result property="empName" column="emp_name"/>
<result property="age" column="age">result>
<result property="sex" column="sex">result>
<result property="email" column="email">result>
<association property="dept" javaType="Dept">
<id property="did" column="did">id>
<result property="deptName" column="dept_name"/>
association>
resultMap>
<select id="getEmpAndDept" resultMap="empAndDeptResultMap">
select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid};
select>
先查询员工的信息
/**
* @Description: 查询员工信息
* @CreateTime: 2023/4/2 11:32
* @Author: HeXin
*/
Emp getEmpByStep(@Param("eid") int eid);
查询语句
<resultMap id="empDeptStepMap" type="Emp">
<id column="eid" property="eid">id>
<result column="empName" property="emp_name">result>
<result column="age" property="age">result>
<result column="sex" property="sex">result>
<result column="email" property="email">result>
<association property="dept"
select="mappers.DeptMapper.getEmpDeptByStep" column="did">
association>
resultMap>
<select id="getEmpByStep" resultMap="empDeptStepMap">
select * from t_emp where eid = #{eid}
select>
再根据员工所对应的部门id查询部门信息
/**
* @Description: 查询员工信息
* @CreateTime: 2023/4/2 11:32
* @Author: HeXin
*/
Dept getEmpDeptByStep(@Param("did") int did);
查询语句
<select id="getEmpDeptByStep" resultType="Dept">
select * from t_dept where did = #{did}
select>
测试代码
@Test
public void testGetEmpAndDept(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = mapper.getEmpAndDept(1);
System.out.println(emp);
}
测试结果
分布查询的好处是延迟加载(懒加载,对某种信息推迟加载,这样的技术也就帮助我们实现了 “按需查询” 的机制),而延迟加载默认在MyBatis中不开启的。
如果需要开启延迟加载,则需要在核心配置文件的全局设置中设置这两个属性
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
settings>
当开启延迟加载之后,可以通过修改association中的fetchType属性的值来手动控制延迟加载的效果
通过查询部门及其员工信息为例。
/**
* @Description:获取部门及其员工信息
* @CreateTime: 2023/4/2 11:54
* @Author: HeXin
*/
Dept getDeptAndEmp(@Param("did") Integer did);
<resultMap id="deptAndEmpResultMap" type="Dept">
<id property="did" column="did"/>
<result property="deptName" column="dept_name"/>
<collection property="emps" ofType="Emp">
<id property="eid" column="eid"/>
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
collection>
resultMap>
<select id="getDeptAndEmp" resultMap="deptAndEmpResultMap">
select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did};
select>
第一步
先根据did查询部门信息
/**
* @Description: 分布查询第一步:查询部门信息
* @CreateTime: 2023/4/2 14:52
* @Author: HeXin
*/
Dept getDeptAndEmpByStepOne(@Param("did") Integer did);
查询语句
<resultMap id="deptAndEmpByStepOneResultMap" type="Dept">
<id property="did" column="did"/>
<result property="deptName" column="dept_name"/>
<collection property="emps"
select="mappers.EmpMapper.getDeptAndEmpByStepTwo"
column="did"
fetchType="lazy">collection>
resultMap>
<select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpByStepOneResultMap">
select * from t_dept where did = #{did};
select>
第二步
根据did查询员工信息
/**
* @Description: 根据did查询员工信息
* @CreateTime: 2023/4/2 11:54
* @Author: HeXin
*/
List<Emp> getDeptAndEmpByStepTwo(@Param("did") Integer did);
查询语句
<select id="getDeptAndEmpByStepTwo" resultType="Emp">
select * from t_emp where did = #{did};
select>
测试代码
@Test
public void testGetDeptAndEmpByStep(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept = mapper.getDeptAndEmpByStepOne(1);
System.out.println("部门名称:"+dept.getDeptName());
System.out.println("部门id:"+dept.getDid());
List<Emp> emps = dept.getEmps();
System.out.println("部门员工:");
emps.forEach(emp -> System.out.println(emp));
}
测试结果
从运行的日志中可以发现,当代码只查询部门的id和名称的时候,只执行了第一句sql语句。而当查询员工信息的时候,两条sql语句才同时执行。
Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题。
下面以根据多条件查询用户信息为例进行演示学习。
/**
* @Description: 多条件查询
* @CreateTime: 2023/4/2 15:23
* @Author: HeXin
*/
List<Emp> getEmpByCondition(Emp emp);
测试代码
@Test
public void testGetEmpByCondition(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
List<Emp> emps = mapper.getEmpByCondition(new Emp(null,"",19,"女","[email protected]"));
emps.forEach(emp-> System.out.println(emp));
}
使用的是xml中的if标签进行sql语句的拼接来解决这个问题。
查询语句
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp where true
<if test="empName != null and empName != ''">
emp_name = #{empName}
if>
<if test="age != null and age !=''">
and age = #{age}
if>
<if test="age != null and age !=''">
and sex = #{sex}
if>
<if test="age != null and age !=''">
and email = #{email}
if>
select>
where后面加上的true(或者恒等式)是为了防止某些条件不成立之后,where后面直接接上and导致sql语句出错,加上之后也不会影响查询结果
如上面报错信息,就是因为empName为空字符串,导致where直接与and相连而报错。所以在使用if标签的时候要格外注意这个问题,但下面的where标签就能很好的解决这个问题
where标签会自动识别是否需要生成where关键字,并且也会自动屏蔽掉内容前多余的and
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<where>
<if test="empName != null and empName != ''">
emp_name = #{empName}
if>
<if test="age != null and age !=''">
and age = #{age}
if>
<if test="age != null and age !=''">
and sex = #{sex}
if>
<if test="age != null and age !=''">
and email = #{email}
if>
where>
select>
按照上面的情况,执行结果会报错,因为empName为空且未加恒等式导致where与and直接相连。
真的是这样吗?看一下运行结果
结果没有报错,而是直接将员工的信息顺利输出,通过观察日志可以发现,sql’语句中自动添加了where,并且去除了内容前多余的and。
当然,这里只是自动生成where,如果将查询的所有条件都变成null或者空字符串,结果又会怎样呢?
观察运行结果和日志发现,这次的sql语句中没有where关键字,所以sql语句就变成了查询全部员工信息。
注意
:where标签不能将其中内容后面多余的and或or去除,只能将内容前多余的and或or去除
trim标签中包含四个属性:
由此可以看出,该标签能很好解决where与and的兼容问题
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<trim prefix="where" prefixOverrides="and|or">
<if test="empName != null and empName != ''">
emp_name = #{empName}
if>
<if test="age != null and age !=''">
and age = #{age}
if>
<if test="age != null and age !=''">
and sex = #{sex}
if>
<if test="age != null and age !=''">
and email = #{email}
if>
trim>
select>
这里只给出了内容前面加and或or关键字。同理,如果在内容后面加上and或or关键字,则将prefixOverrides改为suffixOverrides即可。
这里看起来是三个标签,实则是一套标签,需要组合使用,其作用相当于Java中的if…else…
when标签至少有一个,而otherwise标签之多只能有一个
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<where>
<choose>
<when test="empName != null and empName != ''">
emp_name = #{empName}
when>
<when test="age != null and age != ''">
age = #{age}
when>
<when test="sex != null and sex != ''">
sex = #{sex}
when>
<when test="empName != null and empName != ''">
email = #{email}
when>
<otherwise>
did = 2
otherwise>
choose>
where>
select>
从代码中看出,其内容中并没有加and,因为该标签如果有一个choose条件成立,则其他条件就会被忽略,所以sql语句中有且最多只有一个成立条件,所以不需要加and关键字
foreach标签相当于Java中的for循环,下面将以批量删除和批量添加为例进行演示
foreach标签中有五个常用的属性:
其中,批量删除有两种写法,一种是id in (删除的id集合或数组),另一种是id = id or id…
/**
* @Description: 通过数组实现批量删除
* @CreateTime: 2023/4/2 16:33
* @Author: HeXin
*/
int deleteMoreArray(@Param("eids") Integer[] eids);
删除语句
第一种
<delete id="deleteMoreArray">
delete from t_emp where eid in
<foreach collection="eids" item="eid" separator="," open="(" close=")">
#{eid}
foreach>
delete>
第二种
<delete id="deleteMoreArray">
delete from t_emp where
<foreach collection="eids" item="eid" separator="or">
eid = #{eid}
foreach>
delete>
/**
* @Description:通过集合实现批量添加
* @CreateTime: 2023/4/2 16:55
* @Author: HeXin
*/
int insertMoreByList(@Param("emps") List<Emp> emps);
插入语句
<insert id="insertMoreByList">
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null,#{emp.empName},#{emp.age},#{emp.sex},#{emp.email},1)
foreach>
insert>
sql标签叫做sql片段,可以将常用的sql片段进行记录,后续需要使用的时候,直接引入使用即可。
接着使用trim标签,根据多条件查询用户信息为例继续演示
查询的方法还是一样,这里就不赘述了。
查询语句
<sql id="empColumns">eid,emp_name,sex,email,deptsql>
<select id="getEmpByCondition" resultType="Emp">
select <include refid="empColumns"/> from t_emp
<trim prefix="where" prefixOverrides="and|or">
<if test="empName != null and empName != ''">
emp_name = #{empName}
if>
<if test="age != null and age !=''">
and age = #{age}
if>
<if test="age != null and age !=''">
and sex = #{sex}
if>
<if test="age != null and age !=''">
and email = #{email}
if>
trim>
select>
引用时使用到了include标签,其中的refid属性表示的是引用sql片段的id
缓存是指,将当前查询出来的数据进行一个记录,等到下一次再来查询相同的数据时,会直接去缓存中去拿取,而不再去数据库中拿去。这样的好处时,提高了查询的效率,当然,坏处就是,当在服务器中修改了这些数据中的内容,如果不及时更新或删除缓存,会导致再次查询时出现数据的修改的情况不能及时响应。
MyBatis中的缓存分为一级缓存和二级缓存,其中一级缓存是默认开启的。两者的级别不同,也就是两者的范围不一样。
一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问。
下面以根据员工id查询员工信息为例
/**
* @Description: 根据员工id获取员工信息
* @CreateTime: 2023/4/2 20:56
* @Author: HeXin
*/
Emp getEmpById(@Param("eid") Integer eid);
查询语句
<select id="getEmpById" resultType="Emp">
select * from t_emp where eid = #{eid};
select>
下面将通过不同的测试代码来测试其范围:
测试代码1
@Test
public void testGetEmpById(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
//第一次获取员工信息
CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
Emp emp = mapper.getEmpById(2);
System.out.println(emp);
//第二次获取员工信息
Emp emp1 = mapper.getEmpById(2);
System.out.println(emp1);
}
运行结果
从运行结果中可以看到,虽然我们用同一个mapper生成了两个相同的员工信息时,但是SQL语句只执行了一次。
测试代码2
@Test
public void testGetEmpById(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
//第一次获取员工信息
CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
Emp emp = mapper.getEmpById(2);
System.out.println(emp);
//第二次获取员工信息
CacheMapper mapper1 = sqlSession.getMapper(CacheMapper.class);
Emp emp1 = mapper1.getEmpById(2);
System.out.println(emp1);
}
运行结果
当使用不同的mapper生成两个相同的员工信息时,但SQL语句仍然只执行了一次。
测试代码3
@Test
public void testGetEmpById(){
//第一次获取员工信息
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
Emp emp = mapper.getEmpById(2);
System.out.println(emp);
//第二次获取员工信息
SqlSession sqlSession1 = SqlSessionUtils.getSqlSession();
CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
Emp emp1 = mapper1.getEmpById(2);
System.out.println(emp1);
}
运行结果
当使用不同的SqlSession生成不同的mapper而创建的两个相同的员工数据时,SQL语句执行了两次。
通过上面的三个测试样例,可以得知,一级缓存的范围是在SqlSession内的,只要是同一个SqlSession,在查询相同数据时都会从缓存中拿取数据。
存在使一级缓存失效的四种情况:
任意一次
增删改操作二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取。二次缓存需要手动开启,且二级缓存的范围比一级缓存大。
必须实现
序列化的接口代码演示
准备工作:Emp完善实现Serializable的接口,在映射文件中加入
测试代码
@Test
public void testTwoCache(){
InputStream stream = null;
try {
stream = Resources.getResourceAsStream("mybatisConfig.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream);
//第一次查询
SqlSession sqlSession = build.openSession(true);
CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
System.out.println(mapper.getEmpById(1));
sqlSession.close();
//第二次查询
SqlSession sqlSession1 = build.openSession(true);
CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
System.out.println(mapper1.getEmpById(1));
sqlSession1.close();
}
要像实现此效果,一定要按照开启条件正确开启二级缓存。
两次查询之间执行任意一次的增删改,会使一级和二级缓存同时失效。
在mapper配置文件中添加的cache标签可以设置一些属性:
eviction
属性(默认的是 LRU):缓存回收策略 LRU(Least Recently Used)(最近最少使用的:移除最长时间不被使用的对象)。
flushInterval
属性:刷新间隔,单位毫秒 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。size
属性:引用数目,正整数 代表缓存最多可以存储多少个对象,太大容易导致内存溢出。
<dependency>
<groupId>org.mybatis.cachesgroupId>
<artifactId>mybatis-ehcacheartifactId>
<version>1.2.1version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.3version>
dependency>