老冯课堂笔记mybatis
1.框架概述
1.1 什么是框架
程序开发中的框架往往是对常见功能的封装,通常与具体业务无关,也可以认为是软件的半成品。程序框架理解为基础或者机械标准件(例如螺丝螺母标准的机械部件)。
假如你要造一辆马车,在没有框架的情况下,你需要自己去伐木,去把木头做成木板,木棍,然后组成轮子,门,等部件,然后组装起来。但如果你用了框架,就相当于你有现成的轮子,门等部件,你只需要组装一下就可以了。
一个框架是一组可复用的设计构件。
框架(Framework)是整个或者部分系统的可重用设计,是JavaEE底层技术的封装。
框架是可以被开发者定制的应用骨架。
框架是一个半成品,软件是成品。我们在它的基础上开发出成品(软件)。
2.2 框架解决的问题
解决了技术通用的问题
在JavaEE体系中,有着各种各样的技术。不同的软件企业,根据自身的业务需求选择不同的技术,容易造成应用依赖技术,增加了项目开发实现的复杂性和技术风险性。而框架技术就可以解决上述问题。
提升了开发效率
企业项目中使用框架,只需要专注实现业务需求。使用框架的方便性,提升了开发效率。
提升了系统稳定性
一个成熟的框架,经过了在众多企业项目中的验证使用,稳定性有保障。
2.Mybatis框架介绍
mybatis是Apache软件基金会下的一个开源项目,前身是iBatis框架。2010年这个项目由apache 软件基金会迁移到google code下,改名为mybatis。2013年11月又迁移到了github(GitHub 是一个面向开源及私有 软件项目的托管平台)。
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射(多表)。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。它对 jdbc 的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建 connection、创建 statement、手动设置参数、结果集检索等 jdbc 繁杂的过程代码。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
mybatis的优点
- 简单易学:mybatis本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个SQL映射文件即可。
- 使用灵活:Mybatis不会对应用程序或者数据库的现有设计强加任何影响。SQL语句写在XML里,便于统一管理和优化。
- 解除SQL与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易进行单元测试。SQL语句和代码的分离,提高了可维护性。
mybatis的不足
- 编写SQL语句时工作量很大,尤其是字段多、关联表多时,更是如此。
- SQL语句依赖于数据库,导致数据库移植性差,不能更换数据库。
- 框架还是比较简陋,功能尚有缺失。
官方网站及框架包下载
官网地址:http://www.mybatis.org/mybatis-3/
源码和包下载地址:https://github.com/mybatis/mybatis-3/releases
mybatis框架整体架构
1、配置文件
全局配置文件(核心配置文件):mybatis-config.xml,作用:配置数据源(配置数据库连接信息),引入映射文件
映射文件:XxMapper.xml,作用:配置sql语句、参数、结果集封装类型等
2、SqlSessionFactory
作用:获取SqlSession
通过new SqlSessionFactoryBuilder().build(inputStream)来构建,inputStream:读取配置文件的IO流。
这里涉及到了构建者设计模式
3、SqlSession
表示会话对象,底层封装的是Connection连接对象
作用:执行CRUD操作
4、Executor
执行器,SqlSession通过调用它来完成具体的CRUD(使用执行器执行sql语句)
5、Mapped Statement
在映射文件里面配置,包含3部分内容:
具体的sql,sql执行所需的参数类型,sql执行结果的封装类型
参数类型和结果集封装类型包括3种:
HashMap,基本数据类型,pojo
MyBatis的ORM方式
MyBatis的两种映射方式:
- 通过XML映射
- 通过注解
3.MyBatis框架入门开发
3.1 需求
利用mybatis框架,从MySQL中查询所有的用户
3.2 准备数据
create table user(
id int primary key auto_increment,
username varchar(20) not null,
birthday date,
sex char(1) default '男',
address varchar(50)
);
insert into user values(null,'孙悟空','1980-10-24','男','花果山水帘洞');
insert into user values(null,'白骨精','1992-11-12','女','白虎岭白骨洞');
insert into user values(null,'猪八戒','1983-05-20','男','福临山云栈洞');
insert into user values(null,'蜘蛛精','1995-03-22','女','盘丝洞');
3.3 入门案例
准备pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>MybatisDemo</artifactId>
<groupId>com.darksnow</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>BeginDemo</artifactId>
<dependencies>
<!--mybatis核心包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.0</version>
</dependency>
<!--logback日志包-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.18</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
在resources下面创建核心配置文件:mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.darksnow.dao.UserMapper">
<select id="findAllUsers" resultType="com.darksnow.pojo.User">
select * from user
</select>
</mapper>
在resources下导入logback.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
CONSOLE :表示当前的日志信息是可以输出到控制台的。
-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--输出流对象 默认 System.out 改为 System.err-->
<target>System.out</target>
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%n</pattern>
</encoder>
</appender>
<!-- File是输出的方向通向文件的 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!--日志输出路径-->
<file>E:/code/darksnow-data.log</file>
<!--指定日志文件拆分和压缩规则-->
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--通过指定压缩文件名称,来确定分割文件方式-->
<fileNamePattern>E:/code/darksnow-data2-%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
<!--文件拆分大小-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
</appender>
<!--
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
, 默认debug
<root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
-->
<root level="ALL">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE" />
</root>
</configuration>
创建UserMapper接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import java.util.List;
public interface UserMapper {
/**
* 查询所有用户
*/
List<User> findAllUsers();
}
映射文件(UserMapper.xml)的namespace值 = 接口的全限类名(包.类名)
映射文件(UserMapper.xml)的id值 = 接口中的方法名
创建User实体类
package com.darksnow.pojo;
import java.util.Date;
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
public User() {
}
public User(Integer id, String username, Date birthday, String sex, String address) {
this.id = id;
this.username = username;
this.birthday = birthday;
this.sex = sex;
this.address = address;
}
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 Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}
编写测试类
package com.darksnow.test;
import com.darksnow.dao.UserMapper;
import com.darksnow.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 org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyBatisTest {
/**
* 查询所有用户
*/
@Test
public void test01() throws IOException {
//1.从xml文件中构建SqlSessionFactory
//定义核心配置文件
String resource = "mybatis-config.xml";
//加载核心配置文件,获取输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.从SqlSessionFactory中获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//3.使用SqlSession获取接口的动态代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//4.使用接口对象调用接口中的方法
List<User> userList = mapper.findAllUsers();
//5.遍历集合
for (User user : userList) {
System.out.println(user);
}
//6.关闭会话
sqlSession.close();
}
}
在mybatis中一个会话相当于一次访问数据库的过程,一个会话对象类似于一个Connection连接对象
1.SqlSessionFactoryBuilder:这是一个临时对象,用完就不需要了,通过这个工厂建造类来创建一个会话工程
2.SqlSessionFactory:可以从一个工厂类中得到一个会话对象,一个项目中只需要创建一个会话工厂对象即可,通过会话工厂对象来创建会话对象。
3.SqlSession:每次访问数据库都需要创建一个会话对象,这个会话对象不能共享,访问完成以后需要关闭会话。
在idea中定义模板,以后方便使用
4.MyBatis的核心配置
mybatis-config.xml,是MyBatis的全局配置文件,包含了全局配置信息。
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
注意了:如果配置多项,必须按照上面的顺序进行配置
4.1 properties(属性)
作用
1.加载外部的properties文件
2.通过子标签property设置属性
案例
使用properties属性,配置数据库连接参数
1.在resources下新建一个jdbc.properties文件,写入一下内容:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql:///darksnow
username=root
password=root
2.引入properties
3.修改数据源
4.2 settings(设置)
settings参数由很多,我们暂时只先讲一个:驼峰匹配(mapUnderscoreToCamelCase),翻译过来是映射下划线到驼峰式命名
作用
驼峰匹配:相当于去掉数据库的列名的下划线,和Java进行匹配
案例
所以此时做查询,虽然查询到了,但是映射不上,那么用户名为null
配置驼峰映射,在mybatis-config.xml做如下配置:
<settings>
<!--
mapUnderscoreToCamelCase:驼峰自动映射配置
数据库字段为:user_name 可以映射到实体属性名 userName
true代表开启驼峰自动映射
注意:该配置写在properties下面
-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
4.3 typeAliases(类型别名)
作用
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:
这些全限类名可以通过设置类型别名代替,设置由以下方式:
注意:上图仅截取了部分,剩下的内置类型别名参考网址:https://mybatis.org/mybatis-3/zh/configuration.html#typeAliases
4.4 mappers(映射器)
作用
用来加载Mapper映射文件
用法
5.MyBatis映射文件基本配置
5.1 CRUD操作
映射文件中需要直接书写SQL语句对数据库进行操作,对数据库操作SQL语句主要有CRUD这四类。这四类分别对应映射文件配置中的四类标签:select
,insert
,update
,delete
6.动态代理
6.1 需求案例
需求:模拟某企业用户管理业务,需要包含用户登录,用户删除,用户查询功能,并需要统计每个功能的耗时。
package com.darksnow.proxy;
public interface UserService {
void login();
void delete();
void query();
}
package com.darksnow.proxy;
public class UserServiceImpl implements UserService{
public void login() {
try {
long start = System.currentTimeMillis();
System.out.println("登录");
Thread.sleep(3000);
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void delete() {
try {
long start = System.currentTimeMillis();
System.out.println("删除");
Thread.sleep(4000);
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void query() {
try {
long start = System.currentTimeMillis();
System.out.println("查询");
Thread.sleep(5000);
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.darksnow.proxy;
public class Test {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
userService.login();
userService.delete();
userService.query();
}
}
存在什么问题?
业务对象的每个方法都要进行性能统计,存在大量重复代码
6.2 使用动态代理解决问题
代理就是被代理者没有能力或者不愿意去完成某件事情,需要找个人代替自己去完成这件事,动态代理就是用来对业务功能(方法)进行代理的。
package com.darksnow.proxy;
public interface UserService {
void login();
void delete();
void query();
}
package com.darksnow.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class UserServiceProxyTest {
public static void main(String[] args) {
/*
Proxy类是Java动态代理机制的主类,它提供了用于创建动态代理类和实例的静态方法
代理通常是基于接口实现的
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
参数解释:
ClassLoader loader:定义代理类的类加载器
Class<?>[] interfaces:代理类要实现的接口列表,要求与被代理类的接口一致
InvocationHandler h:具体实现代理逻辑的接口
返回值:
该方法的返回值就是动态生成的代理对象
*/
UserService userService = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{UserService.class}, new InvocationHandler() {
/*
public Object invoke(Object proxy, Method method, Object[] args)
参数解释:
Object proxy:就是代理对象(通常不用)
Method method:代理对象调用的方法
Object[] args:被代理方法中的参数(因为参数个数不确定,所以用一个对象数组表示),如果方法不使用参数,则为null
返回值:
方法被代理后执行的结果
所有代理对象调用的方法,执行是都会经过invoke
因此如果要对某个方法进行代理增强,就可以在这个invoke方法中进行定义。
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取方法名
String name = method.getName();
long start = System.currentTimeMillis();
if ("login".equals(name)) {
System.out.println("登录");
Thread.sleep(3000);
} else if ("delete".equals(name)) {
System.out.println("删除");
Thread.sleep(4000);
} else if ("query".equals(name)) {
System.out.println("查询");
Thread.sleep(5000);
}
long end = System.currentTimeMillis();
System.out.println(name + "耗时:" + (end - start));
return null;
}
});
userService.login();
userService.delete();
userService.query();
}
}
6.3 动态代理用于增强方法
package com.darksnow.proxy;
public interface UserService {
void login();
void delete();
void query();
}
package com.darksnow.proxy;
public class UserServiceImpl implements UserService{
public void login() {
System.out.println("登录操作");
}
public void delete() {
System.out.println("删除操作");
}
public void query() {
System.out.println("查询操作");
}
}
package com.darksnow.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class InstanceProxy {
public <T> T istanceProxy(Class<T> clazz, final Object obj){
/*
Proxy类是Java动态代理机制的主类,它提供了用于创建动态代理类和实例的静态方法
代理通常是基于接口实现的
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
参数解释:
ClassLoader loader:定义代理类的类加载器
Class<?>[] interfaces:代理类要实现的接口列表,要求与被代理类的接口一致
InvocationHandler h:具体实现代理逻辑的接口
返回值:
该方法的返回值就是动态生成的代理对象
*/
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
/*
public Object invoke(Object proxy, Method method, Object[] args)
参数解释:
Object proxy:就是代理对象(通常不用)
Method method:代理对象调用的方法
Object[] args:被代理方法中的参数(因为参数个数不确定,所以用一个对象数组表示),如果方法不使用参数,则为null
返回值:
方法被代理后执行的结果
所有代理对象调用的方法,执行是都会经过invoke
因此如果要对某个方法进行代理增强,就可以在这个invoke方法中进行定义。
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开启事务");
method.invoke(obj,args);
System.out.println("关闭事务");
return null;
}
});
//Lambda表达式写法
// return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, (proxy,method,args) -> {
// System.out.println("开启事务");
// method.invoke(obj,args);
// System.out.println("关闭事务");
// return null;
// });
}
}
public class UserServiceProxyTest {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
userService.login();
System.out.println("---------------------------");
UserService userServiceImpl = new InstanceProxy().istanceProxy(UserService.class, userService);
userServiceImpl.login();
System.out.println("---------------------------");
userServiceImpl.delete();
System.out.println("---------------------------");
userServiceImpl.query();
}
}
7.编写会话工具类
package com.darksnow.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;
public class SqlSessionUtil {
private static SqlSessionFactory factory;
static {
//实例化工厂建造类
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
try {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
factory = builder.build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取sql会话对象
* @return sql会话对象,自动提交事务
*/
public static SqlSession getSession(){
return factory.openSession(true);
}
/**
* 获取sql会话对象
* @param isAutoCommit true:自动提交 false:非自动提交
* @return sql会话对象
*/
public static SqlSession getSession(boolean isAutoCommit){
return factory.openSession(isAutoCommit);
}
/**
* 提交事务并关闭session
*/
public static void commitAndClose(SqlSession sqlSession){
if (sqlSession != null){
sqlSession.commit();
sqlSession.close();
}
}
/**
* 回滚事务并关闭session
*
*/
public static void rollbackAndClose(SqlSession sqlSession){
if (sqlSession != null) {
sqlSession.rollback();
sqlSession.close();
}
}
}
8.MyBatis映射文件配置
8.1 parameterType(重要)
在mybatis中传入的参数的数据类型分为两种:
1.简单数据类型:int,string,long,Date...
2.复杂数据类型:类(JavaBean),Map。
说明:如果传递参数是数组或者集合,底层都会封装到Map集合中。
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import java.util.List;
public interface UserMapper {
/**
* 根据id进行查询
*/
User queryById(Integer id);
}
配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.darksnow.dao.UserMapper">
<!-- 根据id进行查询 -->
<!--
parameterType="int" 表示sql语句参数id类型,int是Integer的别名
MyBatis可以通过类型处理器(TypeHandler),根据接口中的方法
User queryById(Integer id) 参数类型来推断出具体传入的语句的参数类型
parameterType在一定程度上可以省略,但是建议都写上。
parameterType参数类型其实和对应接口参数的类型一致
-->
<select id="queryById" parameterType="int" resultType="User" >
select * from user where id = #{id}
</select>
</mapper>
8.2 自增主键回填(了解)
需求:新增一条数据成功后,将这条数据的主键封装到实体类中,并查看主键的值。
@Test
public void testSaveUser() {
User user = new User();
user.setUserName("蔡徐坤");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("中国");
Integer rs = mapper.saveUser(user);
System.out.println(user.getId());
sqlSession.commit();
sqlSession.close();
}
直接获取id的结果会为null。
有两种方式可以实现添加号数据直接将主键封装到实体类对象中:
方法一
<!-- 添加一个用户 -->
<!-- #{userName},#{birthday},#{sex},#{address} 参考的 User 这个JavaBean属性名 -->
<insert id="saveUser" parameterType="User">
insert into user values(null,#{userName},#{birthday},#{sex},#{address})
<!--
keyColumn:主键在表中对应的列名
keyProperty:主键在实体类中对应的属性名
resultType:主键的数据类型
order:
BEFORE:先给实体类中的属性名(keyProperty)赋值,然后再执行插入语句
AFTER:在添加语句后执行查询主键的语句
-->
<selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
</insert>
方式二
<!-- 添加一个用户 -->
<!-- #{userName},#{birthday},#{sex},#{address} 参考的 User 这个JavaBean属性名 -->
<!--
useGeneratedKeys:true获取自动生成的主键,相当于select last_insert_id()
keyColumn:主键在表中对应的列名
keyProperty:主键在实体类中对应的属性名
-->
<insert id="saveUser" useGeneratedKeys="true" keyColumn="id" keyProperty="id" parameterType="User">
insert into user values(null,#{userName},#{birthday},#{sex},#{address})
</insert>
说明:方式二只适用于支持自动增长主键类型的数据库,比如MySQL,SQL Server。个人推荐使用方式一
8.3 传递单个参数与多个参数(重点)
主要是针对简单类型的数据(int,string,long,date)等进行入参处理
单个参数
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import java.util.List;
public interface UserMapper {
/**
* 根据id进行查询
*/
User queryById(Integer id);
}
通过#{参数名}接收
<select id="queryById" parameterType="int" resultType="User" >
select * from user where id = #{id}
</select>
通过#{任意变量名}接收
<select id="queryById" parameterType="int" resultType="User" >
select * from user where id = #{darksnow}
</select>
结论
当接口方法传入一个参数时,mybatis不做特殊处理,#{任意变量名}都可以接收,但是个人建议和名称和接口方法中的参数名一致
多个参数
需求:根据用户名和性别查询用户
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import java.util.List;
public interface UserMapper {
User queryByUserNameAndSex(String userName, String sex);
}
配置文件
<select id="queryByUserNameAndSex" resultType="User">
select * from user where user_name = #{username} and sex = #{sex}
</select>
测试类
@Test
public void queryByUserNameAndSex() {
User user = mapper.queryByUserNameAndSex("孙悟空", "男");
System.out.println(user);
sqlSession.close();
}
结果
解决方案
1.使用参数索引获取:arg0,arg1(了解)
<select id="queryByUserNameAndSex" resultType="User">
select * from user where user_name = #{arg0} and sex = #{arg1}
</select>
2.使用参数位置获取:param1,param2(了解)
<select id="queryByUserNameAndSex" resultType="User">
select * from user where user_name = #{param1} and sex = #{param2}
</select>
3.使用命名参数获取,明确指定传入参数的名称(重要)
步骤一:在接口中传入参数通过@Param指定参数名称
package com.darksnow.dao;
import com.darksnow.pojo.User;
import java.util.List;
public interface UserMapper {
User queryByUserNameAndSex(@Param("username") String userName, @Param("sex") String sex);
}
步骤二:在接收参数时,通过指定的名称获取参数值
<select id="queryByUserNameAndSex" resultType="User">
select * from user where user_name = #{username} and sex = #{sex}
</select>
8.4 pojo参数(重点)
使用pojo来传递参数(JavaBean)
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserMapper {
Integer saveUser(User user);
}
映射文件
<insert id="saveUser" parameterType="User">
insert into user values(null,#{userName},#{birthday},#{sex},#{address})
</insert>
测试类
@Test
public void testSaveUser() {
User user = new User();
user.setUserName("蔡徐坤");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("中国");
Integer rs = mapper.saveUser(user);
sqlSession.commit();
sqlSession.close();
}
8.5 HashMap参数
需求:模拟用户登录,登录方法参数是Map集合,泛型都是String类型,分别表示用户名和性别
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserMapper {
User login(Map<String,String> map);
}
配置文件
<select id="login" resultType="User">
select * from user where user_name=#{username} and sex=#{sex}
</select>
测试类
@Test
public void login() {
Map<String, String> map = new HashMap<>();
map.put("username","孙悟空");
map.put("sex","男");
User user = mapper.login(map);
System.out.println(user);
}
注意
参数map中key要与SQL语句中#{}的取值名称一致!!!
8.6 参数的获取
参数值的获取指的是,sql语句获取方法中传入的参数,有两种方式:#{},${}
#{}与${}取值的后台处理的区别
#{}取值
${取值}
${}取值的应用场景
在一些特殊的应用场景中,需要对SQL语句部分(不是参数)进行拼接,这个时候就需要使用${}来进行拼接,比如:
1.假设企业开发中随着数据量的增大,往往会将数据表按照年份进行分表,比如:
2023_user,2024_user,...... 对这些表进行查询就需要动态的把年份传入进来,而年份是表名的一部分,并不是参数,JDBC无法对其进行预编译,所以就只能使用${}进行拼接。
select * from ${year}_user;
2.根据表名查询数据总条数
select count(*) from ${tableName};
总结来说:如果需要涉及到sql中的不是查询条件,只能使用${}拼接
需求:查询指定表中的总记录数
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserMapper {
int selectCountByTableName(@Param("tableName") String table);
}
配置文件
<select id="selectCountByTableName" resultType="int">
select count(*) from ${tableName}
</select>
测试类
@Test
public void selectCountByTableName() {
int rs = mapper.selectCountByTableName("user");
System.out.println(rs);
sqlSession.close();
}
8.7 ${}取值注意事项
${}获取单个值
获取单个值时,最好通过命名参数的形式获取,如果不指定参数,也可以使用${value}来获取传入参数
8.8 模糊查询
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserMapper {
//需求:模糊查询地址中含有 山 的用户
List<User> queryUsersByAddressLike(@Param("address") String address);
}
配置文件
<!--
需求:模糊查询地址中含有 山 的用户
1.select * from user where address like '%#{address}%' 这里时拼接,不能使用#{},必须使用${}
select * from user where address like '%${address}%' => select * from user where address like '%山%'
但是我们发现存在SQL注入问题
2.我们实际开发中必须考虑SQL注入问题,而且还必须实现拼接,可以使用mysql自带的拼接函数:concat
select * from user where address like concat('%',#{address},'%')
3.select * from user where address like "%"#{address}"%"
#{},防止sql注入
${},不能防止sql注入
-->
<select id="queryUsersByAddressLike" resultType="User">
select * from user where address like "%"#{address}"%"
</select>
测试类
@Test
public void queryUsersByAddressLike() {
List<User> list = mapper.queryUsersByAddressLike("山");
System.out.println(list);
sqlSession.close();
}
8.9 结果映射
在使用原生的JDBC操作时,对于结果集ResultSet,需要手动处理,mybatis框架提供了resultType和resultMap来对结果集进行封装。
resultType
1.返回值是简单类型
例如:int,string,...
2.返回值为一个pojo对象时
3.返回值为List时
当返回值为List集合时,resultType需要设置成集合中存储的具体的JavaBean对象数据类型(泛型)
4.返回值为Map时(一条结果)
接口方法
package com.darksnow.dao;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
public interface UserMapper {
//需求:查询id是1的数据,将查询的结果封装到Map<String,Object>中
Map<String,Object> selectByIdReturnMap(Integer id);
}
配置文件
<select id="selectByIdReturnMap" resultType="Map">
select * from user where id = #{id}
</select>
测试方法
@Test
public void selectByIdReturnMap() {
Map<String, Object> map = mapper.selectByIdReturnMap(1);
System.out.println(map);
sqlSession.close();
}
测试结果
{birthday=1980-10-24, address=花果山水帘洞, user_name=孙悟空, sex=男, id=1}
通过上述结果我们发现如果返回一条数据存放到Map中,那么表中的列名会作为Map集合的key,结果作为Map集合的value
5.返回值为Map时(多条结果)
接口方法
package com.darksnow.dao;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
public interface UserMapper {
//需求:查询user表中的所有数据并封装到Map<String,User>集合中
//需要在接口的方法上使用注解@MapKey指定数据表中哪一列作为Map集合的key,否则mybatis不知道具体哪个列作为Map集合的key
@MapKey("id")
Map<String,User> selectReturnMap();
}
配置文件
<select id="selectReturnMap" resultType="Map">
select * from user
</select>
测试方法
@Test
public void selectReturnMap() {
Map<String, User> map = mapper.selectReturnMap();
System.out.println(map);
sqlSession.close();
}
测试结果
{
16={birthday=2023-02-21, address=中国, user_name=蔡徐坤, sex=男, id=16},
1={birthday=1980-10-24, address=花果山水帘洞, user_name=孙悟空, sex=男, id=1},
2={birthday=1992-11-12, address=白虎岭白骨洞, user_name=白骨精, sex=女, id=2},
3={birthday=1983-05-20, address=福临山云栈洞, user_name=猪八戒, sex=男, id=3},
4={birthday=1995-03-22, address=盘丝洞, user_name=蜘蛛精, sex=女, id=4}
}
resultMap
resultMap可以解决两大问题:
1.pojo属性名和表结构字段名不一致的问题
2.可以完成高级查询,比如说:一对一,一对多,多对多(后面会讲)
需求:使用resultMap完成结果集的封装
1.将驼峰命名注释掉,注释掉之后我们的数据库表字段名为user_name,但是JavaBean的属性为userName,此时数据无法封装,现在尝试使用resultMap来解决
2.配置resultMap
<!--
resultMap标签上的属性
id:resultMap标签的唯一标识,不能重复,一般使用被引用
type:结果集的封装类型,这里是封装到User
autoMapping:操作单表时,不配置默认为true,如果JavaBean对象中的属性名和表中字段名相同,则自动映射
-->
<resultMap id="userResultMap" type="User" autoMapping="true">
<!--
配置主键映射关系
column 数据表的字段名
property JavaBean中的属性名
-->
<id column="id" property="id"/>
<!--
配置指定字段与属性的映射关系
column 数据表的字段名
property JavaBean中的属性名
-->
<result column="user_name" property="userName"/>
</resultMap>
<select id="queryById" parameterType="int" resultMap="userResultMap" >
select * from user where id = ${id}
</select>
3.接口方法
package com.darksnow.dao;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
public interface UserMapper {
User queryById(@Param("id") Integer id);
}
4.测试方法
@Test
public void testQueryById() throws IOException {
User user = mapper.queryById(1);
System.out.println(user);
sqlSession.close();
}
8.10 动态SQL
ognl表达式
o1 or o2 满足一个即可
o1 and o2 都得满足
o1 == o2,o1 eq o2 判断是否相等
o1 != o2,o1 neq o2 不相等
o1 lt o2 小于
o1 lte o2 小于等于,gt(大于),gte(大于等于)
o1 in o2
o1 not in o2
o1 + o2 (- * / %)
!o1,not o1 非,取反
o1.方法 调用对象方法
o1.属性 调用对象属性
o1[index] 根据索引取值
if标签
格式:
<if test="判断条件">
满足条件执行的代码
</if>
说明:
1.if标签:判断语句,用于进行逻辑判断的,如果判断条件为true,则执行if标签的文本内容
2.test属性:用来填写表达式,支持ognl表达式
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
public interface UserMapper {
//根据用户名进行模糊查询,且性别必须为男
List<User> queryLikeUserName(@Param("userName") String userName);
}
配置文件
<resultMap id="userResultMap" type="User" autoMapping="true">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
</resultMap>
<select id="queryLikeUserName" resultMap="userResultMap">
select * from user where sex = '男'
<if test="userName != null and userName.trim() != ''">
and user_name like "%"#{userName}"%"
</if>
</select>
测试方法
@Test
public void queryLikeUserName() {
List<User> list = mapper.queryLikeUserName(null);
System.out.println(list);
sqlSession.close();
}
choose,when,otherwise标签
choose标签:分支选择(多选一,遇到成立条件即停止)
when子标签:编写条件,不管有多少个when条件,一旦其中一个条件成立,后面when条件都不执行。
otherwise子标签:当所有条件都不满足时,才会执行该条件。
test属性:编写ognl表达式
需求
编写一个查询方法,设置两个参数,一个用户名,一个地址
根据用户名或者地址查询所有男性用户:
如果输入了用户名则按照用户名模糊查询
否则就按照地址查询,两个条件只能成立一个
如果都不输入就查询用户名为“孙悟空”的用户
分析:
查询所有男性用户,如果输入了用户名则按照用户名模糊查询
select * from user where sex = "男" and user_name like "%"#{username}"%";
查询所有男性用户,如果输入了地址则按照地址查询
select * from user where sex = "男" and address = #{address};
查询所有男性用户,如果都不输入就查询用户名为“孙悟空”的用户
select * from user where sex = "男" and user_name = "孙悟空";
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
public interface UserMapper {
List<User> queryByUserNameOrAddress(@Param("username") String username,@Param("address") String address);
}
配置文件
<resultMap id="userResultMap" type="User" autoMapping="true">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
</resultMap>
<select id="queryByUserNameOrAddress" resultMap="userResultMap">
select * from user where sex = "男"
<choose>
<when test="username != null and username.trim() != ''">
and user_name like "%"#{username}"%"
</when>
<when test="address != null and address.trim() != ''">
and address = #{address}
</when>
<otherwise>
and user_name = "孙悟空"
</otherwise>
</choose>
</select>
测试方法
@Test
public void queryByUserNameOrAddress() {
List<User> list = mapper.queryByUserNameOrAddress("", "中国");
System.out.println(list);
sqlSession.close();
}
where标签
where标签:拼接多条查询时,能够添加where标签,可以去除多余的and或者or
需求:按照下面的条件查询所有用户
如果输入了用户名按照用户名进行查询
select * from user where user_name = #{username}
如果输入了地址按照地址进行查询
select * from user where address = #{address}
如果两者都输入,两个条件都要成立
select * from user where user_name = #{username} and address = #{address}
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
public interface UserMapper {
List<User> queryByUserNameOrAddress(@Param("username") String username,@Param("address") String address);
}
配置文件
<!-- <select id="queryByUserNameAndAddress" resultMap="userResultMap">
select * from user where
<if test="username != null and username.trim() != ''">
user_name = #{username}
</if>
<if test="address != null and address.trim() != ''">
address = #{address}
</if>
</select> -->
<!--
上面的写法会出问题,可以使用where标签来解决
因为它或去掉多余的and或者or
但是得在前面!!!
-->
<resultMap id="userResultMap" type="User" autoMapping="true">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
</resultMap>
<select id="queryByUserNameAndAddress" resultMap="userResultMap">
select * from user
<where>
<if test="username != null and username.trim() != ''">
user_name = #{username}
</if>
<if test="address != null and address.trim() != ''">
and address = #{address}
</if>
</where>
</select>
测试方法
@Test
public void queryByUserNameAndAddress() {
List<User> list = mapper.queryByUserNameAndAddress("蔡徐坤", "中国");
System.out.println(list);
sqlSession.close();
}
set标签
set标签:在update语句中,可以添加一个set标签,会将动态sql最后多余的逗号去掉。
需求:修改用户的信息,如果参数user中的某个属性为null,则不修改
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
public interface UserMapper {
void updateSelectiveUser(User user);
}
配置文件
<update id="updateSelectiveUser">
update user
<set>
<if test="userName != null and userName.trim() != ''">
user_name = #{userName},
</if>
<if test="birthday != null">
birthday = #{birthday},
</if>
<if test="sex != null and sex.trim() != ''">
sex = #{sex},
</if>
<if test="address != null and address.trim() != ''">
address = #{address}
</if>
</set>
where id = #{id}
</update>
测试方法
@Test
public void updateSelectiveUser() {
User user = new User();
user.setId(16);
user.setUserName("爱坤坤");
user.setSex("女");
user.setBirthday(new Date());
user.setAddress("");
mapper.updateSelectiveUser(user);
sqlSession.commit();
sqlSession.close();
}
foreach标签
foreach标签:遍历集合或者数组
<foreach collection="集合名或者数组名" item="元素" separator="分隔符" open="以什么开始" close="以什么结束">
#{id}
</foreach>
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
public interface UserMapper {
//这里一定要加@Param
List<User> queryByIds(@Param("arrIds") Integer[] arrIds);
}
配置文件
<resultMap id="userResultMap" type="User" autoMapping="true">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
</resultMap>
<select id="queryByIds" resultMap="userResultMap">
select * from user where id in
<foreach collection="arrIds" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</select>
SQL片段
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.darksnow.dao.UserMapper">
<!--
SQL片段
可以把重复的sql语句提出来,需要时引用即可
id:唯一标识
文本:sql语句
-->
<sql id="select_user">select * from user</sql>
<resultMap id="userResultMap" type="User" autoMapping="true">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
</resultMap>
<select id="queryByIds" resultMap="userResultMap">
<!-- 关联使用sql片段 -->
<include refid="select_user"/>
where id in
<foreach collection="arrIds" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</select>
</mapper>
9.多表查询
9.0 准备SQL
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for tb_item
-- ----------------------------
DROP TABLE IF EXISTS `tb_item`;
CREATE TABLE `tb_item` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`item_name` varchar(32) NOT NULL COMMENT '商品名称',
`item_price` float(6,1) NOT NULL COMMENT '商品价格',
`item_detail` text COMMENT '商品描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of tb_item
-- ----------------------------
INSERT INTO `tb_item` VALUES ('1', 'iPhone 6', '5288.0', '苹果公司新发布的手机产品。');
INSERT INTO `tb_item` VALUES ('2', 'iPhone 6 plus', '6288.0', '苹果公司发布的新大屏手机。');
-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_name` varchar(100) DEFAULT NULL COMMENT '用户名',
`password` varchar(100) DEFAULT NULL COMMENT '密码',
`name` varchar(100) DEFAULT NULL COMMENT '姓名',
`age` int(10) DEFAULT NULL COMMENT '年龄',
`sex` int(11) DEFAULT NULL COMMENT '0-女 1-男',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of tb_user
-- ----------------------------
INSERT INTO `tb_user` VALUES ('1', 'zhangsan', '123456', '张三', '30', '1');
INSERT INTO `tb_user` VALUES ('2', 'lisi', '123456', '李四', '21', '0');
INSERT INTO `tb_user` VALUES ('3', 'wangwu', '123456', '王五', '22', '1');
INSERT INTO `tb_user` VALUES ('4', 'zhangwei', '123456', '张伟', '20', '1');
INSERT INTO `tb_user` VALUES ('5', 'lina', '123456', '李娜', '28', '0');
INSERT INTO `tb_user` VALUES ('6', '蔡徐坤', '123', '小菜', '18', '1');
-- ----------------------------
-- Table structure for tb_order
-- ----------------------------
DROP TABLE IF EXISTS `tb_order`;
CREATE TABLE `tb_order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL,
`order_number` varchar(20) NOT NULL COMMENT '订单号',
PRIMARY KEY (`id`),
KEY `FK_orders_1` (`user_id`),
CONSTRAINT `FK_orders_1` FOREIGN KEY (`user_id`) REFERENCES `tb_user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of tb_order
-- ----------------------------
INSERT INTO `tb_order` VALUES ('1', '1', '20140921001');
INSERT INTO `tb_order` VALUES ('2', '2', '20140921002');
INSERT INTO `tb_order` VALUES ('3', '1', '20140921003');
-- ----------------------------
-- Table structure for tb_orderdetail
-- ----------------------------
DROP TABLE IF EXISTS `tb_orderdetail`;
CREATE TABLE `tb_orderdetail` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_id` int(32) DEFAULT NULL COMMENT '订单号',
`item_id` int(32) DEFAULT NULL COMMENT '商品id',
`total_price` double(20,0) DEFAULT NULL COMMENT '商品总价',
`status` int(11) DEFAULT NULL COMMENT '状态',
PRIMARY KEY (`id`),
KEY `FK_orderdetail_1` (`order_id`),
KEY `FK_orderdetail_2` (`item_id`),
CONSTRAINT `FK_orderdetail_1` FOREIGN KEY (`order_id`) REFERENCES `tb_order` (`id`),
CONSTRAINT `FK_orderdetail_2` FOREIGN KEY (`item_id`) REFERENCES `tb_item` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of tb_orderdetail
-- ----------------------------
INSERT INTO `tb_orderdetail` VALUES ('1', '1', '1', '5288', '1');
INSERT INTO `tb_orderdetail` VALUES ('2', '1', '2', '6288', '1');
INSERT INTO `tb_orderdetail` VALUES ('3', '2', '2', '6288', '1');
INSERT INTO `tb_orderdetail` VALUES ('4', '3', '1', '5288', '1');
9.1 表介绍和关系介绍
tb_user:用户表
tb_order:订单表
tb_item:商品表
tb_orderdetail:订单详情表
tb_user与tb_order表关系:
用户表 <==> 订单表 一对多,一个人可以下多个订单
订单表 <==> 用户表 一对一,一个订单只能属于一个人
总结:tb_user和tb_order属于一对多的关系,需要将一的一方tb_user的主键作为多的一方tb_order的外键维护关系
tb_order与tb_item表关系:
订单表 <==> 商品表:一个订单可以有多个商品
商品表 <==> 订单表:一个商品可以在多个订单上
总结:tb_order和tb_1item属于多对多的关系,需要创建中间表tb_orderdetail维护两表的关系,并且将两表的主键作为中间
表的外键。
9.2 一对一查询
需求:通过订单编号20140921003查询出订单信息,并查询出下单人信息。
一个订单编号对应一个订单,一个订单只能属于一个人,所以上述需求为一对一的实现。
接口
package com.darksnow.dao;
import com.darksnow.pojo.Order;
import org.apache.ibatis.annotations.Param;
public interface OrderMapper {
//根据订单号查询订单以及下单人的信息
Order queryOrderAndUserByOrderNumber(@Param("orderNumber") String orderNumber);
}
实体类
package com.darksnow.pojo;
public class Order {
private Integer id;
private String orderNumber;
//关联User对象 tb_order.user_id(外键) 引用 tb_user.id(主键)
private User user;
//省略getter,setter,toString方法
}
package com.darksnow.pojo;
import java.util.Date;
public class User {
private Integer id;
private String userName;
private String password;
private String name;
private Integer age;
private Integer sex;
//省略getter,setter,toString方法
}
配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.darksnow.dao.OrderMapper">
<resultMap id="OrderResultMap" type="Order" autoMapping="true">
<id column="oid" property="id"/>
<result column="order_number" property="orderNumber"/>
<!--
一对一映射关系使用子标签association来表示引用另外一个pojo类的对象,这里引用的是User对象
property="user" 表示在Order类中的引用的User类的对象成员变量名
javaType="User" 表示引用的user对象属于User类型
大白话:Order对象中引用的是User对象(javaType),其属性名叫user(property)
-->
<association property="user" javaType="User" autoMapping="true">
<!-- 下面是关于user表中的属性映射 -->
<id column="uid" property="id"/>
<result column="user_name" property="userName"/>
</association>
</resultMap>
<select id="queryOrderAndUserByOrderNumber" resultMap="OrderResultMap">
select
tbo.id as oid,
tbo.order_number,
tbu.id as uid,
tbu.user_name,
tbu.password,
tbu.name,
tbu.age,
tbu.sex
from
tb_order tbo
inner join
tb_user tbu
on
tbo.user_id = tbu.id
where
tbo.order_number = #{orderNumber}
</select>
</mapper>
测试方法
@Test
public void queryOrderAndUserByOrderNumber() {
SqlSession session = SqlSessionUtil.getSession();
OrderMapper mapper = session.getMapper(OrderMapper.class);
Order order = mapper.queryOrderAndUserByOrderNumber("20140921003");
System.out.println(order);
SqlSessionUtil.commitAndClose(session);
}
9.3 一对多查询
需求:查询id为1的用户及其订单信息
一个用户可以有多个订单,一个订单只能属于一个用户,用户(1) <==> 订单(n)
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.Param;
public interface UserMapper {
//根据用户id查询用户及其订单信息
User oneToManyQuery(@Param("id") Integer id);
}
实体类
package com.darksnow.pojo;
import java.util.List;
public class User {
private Integer id;
private String userName;
private String password;
private String name;
private Integer age;
private Integer sex;
//一个用户可以对应多个订单
private List<Order> orders;
//省略getter setter toString方法
}
配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.darksnow.dao.UserMapper">
<resultMap id="oneToManyResultMap" type="User" autoMapping="true">
<id column="uid" property="id"/>
<result column="user_name" property="userName"/>
<!--
一对多映射关系使用子标签collection来表示引用多的一方的pojo类的对象,这里引用的是Order对象
property="orders" 表示在User类中的引用的List<Order>的对象成员变量名
javaType="List" 表示引用的List<Order>对象属于List类型
大白话:User对象中引用的是List<Order>对象(javaType),其属性名叫orders(property)
ofType="Order" 表示List集合中存储的元素的数据类型
-->
<collection property="orders" javaType="List" ofType="Order" autoMapping="true">
<id column="oid" property="id"/>
<result column="order_number" property="orderNumber"/>
</collection>
</resultMap>
<select id="oneToManyQuery" resultMap="oneToManyResultMap">
select
tbu.id as uid,
tbu.user_name,
tbu.password,
tbu.name,
tbu.age,
tbu.sex,
tbo.id as oid,
tbo.order_number
from
tb_user tbu
inner join
tb_order tbo
on
tbu.id = tbo.user_id
where
tbu.id = 1;
</select>
</mapper>
测试方法
@Test
public void oneToManyQuery() {
SqlSession session = SqlSessionUtil.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.oneToManyQuery(1);
System.out.println(user);
SqlSessionUtil.commitAndClose(session);
}
9.4 多对多查询
需求:查询订单号为20140921001的订单详情信息,订单详情信息 = 订单 + 商品
关系:一个订单对应多个订单详情,一个订单详情对应一种商品
实体类
package com.darksnow.pojo;
import java.util.List;
public class Order {
private Integer id;
private String orderNumber;
//关联User对象 tb_order.user_id(外键) 引用 tb_user.id(主键)
private User user;
//一个订单包含多个订单详情信息
private List<Orderdetail> detailList;
//省略getter,setter,toString
}
package com.darksnow.pojo;
public class Orderdetail {
private Integer id;
private Double totalPrice;
private Integer status;
//一个订单详情包含一个商品
private Item item;
//省略getter,setter,toString
}
package com.darksnow.pojo;
public class Item {
private Integer id;
private String item_name;
private Double item_price;
private String item_detail;
//省略getter,setter,toString
}
接口
package com.darksnow.dao;
import com.darksnow.pojo.Order;
import org.apache.ibatis.annotations.Param;
public interface OrderMapper {
Order queryOrderAndDetailByOrderNumber(@Param("ordeNumber") String orderNumber);
}
配置文件
<resultMap id="orderAndDetailMap" type="Order" autoMapping="true">
<id property="id" column="oid"/>
<result property="orderNumber" column="order_number"/>
<!-- 多个订单详情 detailList -->
<collection property="detailList" javaType="List" ofType="Orderdetail" autoMapping="true">
<id property="id" column="detailId"/>
<result property="totalPrice" column="total_price"/>
<!-- 一个商品 -->
<association property="item" javaType="Item" autoMapping="true">
<id property="id" column="itemId"/>
</association>
</collection>
</resultMap>
<select id="queryOrderAndDetailByOrderNumber" resultMap="orderAndDetailMap">
select
tbo.id as oid,
tbo.order_number,
detail.id as detailId,
detail.total_price,
detail.status,
item.id as itemId,
item.item_name,
item.item_price,
item.item_detail
from
tb_order tbo
inner join tb_orderdetail detail on tbo.id = detail.order_id
inner join tb_item item on detail.item_id = item.id
where
tbo.order_number = #{ordeNumber}
</select>
测试方法
@Test
public void queryOrderAndDetailByOrderNumber() {
SqlSession session = SqlSessionUtil.getSession();
OrderMapper mapper = session.getMapper(OrderMapper.class);
Order order = mapper.queryOrderAndDetailByOrderNumber("20140921001");
System.out.println(order);
SqlSessionUtil.commitAndClose(session);
}
结果
Order{
id=1,
orderNumber='20140921001',
user=null,
detailList=[
Orderdetail{
id=1,
totalPrice=5288.0,
status=1,
item=Item{
id=1,
item_name='iPhone 6',
item_price=5288.0,
item_detail='苹果公司新发布的手机产品。'
}
},
Orderdetail{
id=2,
totalPrice=6288.0,
status=1,
item=Item{
id=2,
item_name='iPhone 6 plus',
item_price=6288.0,
item_detail='苹果公司发布的新大屏手机。'
}
}
]
}
9.5 多对多扩展
需求:根据订单编号20140921001,查询订单信息,查询订单所属用户信息,订单中的商品详细信息(在原来的9.5笔记的基础上多了个所属用户信息)
接口
package com.darksnow.dao;
import com.darksnow.pojo.Order;
import org.apache.ibatis.annotations.Param;
public interface OrderMapper {
Order queryOderAndDetailAndUserByOrderNumber(@Param("ordeNumber") String orderNumber);
}
配置文件
<resultMap id="orderAndDetailAndUserMap" type="Order" autoMapping="true" >
<id property="id" column="oid"/>
<result property="orderNumber" column="order_number"/>
<!-- Oder-User :一对一关联 -->
<association property="user" javaType="User" autoMapping="true">
<id property="id" column="uid"/>
<result property="userName" column="user_name"/>
</association>
<!-- 多个订单详情 detailList -->
<collection property="detailList" javaType="List" ofType="Orderdetail" autoMapping="true">
<id property="id" column="detailId"/>
<result property="totalPrice" column="total_price"/>
<!-- 一个商品 -->
<association property="item" javaType="Item" autoMapping="true">
<id property="id" column="itemId"/>
</association>
</collection>
</resultMap>
<select id="queryOderAndDetailAndUserByOrderNumber" resultMap="orderAndDetailAndUserMap">
select
tbo.id as oid,
tbo.order_number,
detail.id as detailId,
detail.total_price,
detail.status,
item.id as itemId,
item.item_name,
item.item_price,
item.item_detail,
tbu.id as uid,
tbu.user_name,
tbu.password,
tbu.name,
tbu.age,
tbu.sex
from
tb_order tbo
inner join tb_orderdetail detail on tbo.id = detail.order_id
inner join tb_item item on detail.item_id = item.id
inner join tb_user tbu on tbu.id = tbo.user_id
where
tbo.order_number = #{ordeNumber}
</select>
测试方法
@Test
public void queryOderAndDetailAndUserByOrderNumber() {
SqlSession session = SqlSessionUtil.getSession();
OrderMapper mapper = session.getMapper(OrderMapper.class);
Order order = mapper.queryOderAndDetailAndUserByOrderNumber("20140921001");
System.out.println(order);
SqlSessionUtil.commitAndClose(session);
}
结果
Order{
id=1,
orderNumber='20140921001',
user=User{
id=1,
userName='zhangsan',
password='123456',
name='张三',
age=30,
sex=1,
orders=null
},
detailList=[
Orderdetail{
id=1,
totalPrice=5288.0,
status=1,
item=Item{
id=1,
item_name='iPhone 6',
item_price=5288.0,
item_detail='苹果公司新发布的手机产品。'
}
},
Orderdetail{
id=2,
totalPrice=6288.0,
status=1,
item=Item{
id=2,
item_name='iPhone 6 plus',
item_price=6288.0,
item_detail='苹果公司发布的新大屏手机。'
}
}
]
}
10.注解开发
10.1 注解实现CRUD
开启驼峰命名!!!
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserMapper {
//1.新增数据
@Insert("insert into tb_user values(null,#{userName},#{password},#{name},#{age},#{sex})")
void saveUser(User user);
//2.根据id删除用户
@Delete("delete from tb_user where id = #{id}")
void deleteUserById(Integer id);
//3.修改id为6的用户数据
@Update("update tb_user set user_name=#{userName},age=#{age} where id = #{id}")
void updateUser(User user);
//4.查询所有用户数据
@Select("select * from tb_user")
List<User> queryAllUsers();
}
测试方法
package com.darksnow.test;
import com.darksnow.dao.UserMapper;
import com.darksnow.pojo.User;
import com.darksnow.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.Test;
import java.util.List;
public class AnnotationTest {
@Test
public void saveUser() {
SqlSession session = SqlSessionUtil.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = new User();
user.setUserName("坤坤");
user.setPassword("123456");
user.setName("爱坤");
user.setAge(18);
user.setSex(1);
mapper.saveUser(user);
SqlSessionUtil.commitAndClose(session);
}
@Test
public void deleteUserById() {
SqlSession session = SqlSessionUtil.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.deleteUserById(14);
SqlSessionUtil.commitAndClose(session);
}
@Test
public void updateUser() {
SqlSession session = SqlSessionUtil.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = new User();
user.setId(6);
user.setUserName("坤坤");
user.setAge(20);
mapper.updateUser(user);
SqlSessionUtil.commitAndClose(session);
}
@Test
public void queryAllUsers() {
SqlSession session = SqlSessionUtil.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.queryAllUsers();
System.out.println(users);
SqlSessionUtil.commitAndClose(session);
}
}
10.2 返回新增数据的id(自增主键回填)
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserMapper {
@Insert("insert into tb_user values(null,#{userName},#{password},#{name},#{age},#{sex})")
/*
useGeneratedKeys:true获取自动生成的主键
keyColumn:主键在表中对应的列名
keyProperty:主键在实体类中对应的属性名
*/
@Options(useGeneratedKeys = true,keyColumn = "id",keyProperty = "id")
void saveUser(User user);
}
测试方法
@Test
public void saveUser() {
SqlSession session = SqlSessionUtil.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = new User();
user.setUserName("爱坤坤");
user.setPassword("123456");
user.setName("爱坤");
user.setAge(18);
user.setSex(1);
mapper.saveUser(user);
System.out.println(user.getId());
SqlSessionUtil.commitAndClose(session);
}
10.3 注解实现别名映射
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserMapper {
@Select("select * from tb_user")
@Results(value = {
@Result(column = "user_name",property = "userName")
})
void saveUser(User user);
}
测试方法
@Test
public void queryAllUsers() {
SqlSession session = SqlSessionUtil.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.queryAllUsers();
System.out.println(users);
SqlSessionUtil.commitAndClose(session);
}
10.4 注解实现动态SQL
方式一
ProviderUtil
package com.darksnow.utils;
import org.apache.ibatis.annotations.Param;
public class ProviderUtil {
//如果用户名为空,那么只根据性别查询,如果用户名不为空,那么根据性别查询的同时,根据用户名进行模糊查询
public String queryUsersBySexOrUsernameSQL(@Param("sex") Integer sex, @Param("username") String username){
String sql = "select * from tb_user where sex = #{sex}";
if (username != null && !"".equals(username)) {
sql += " and user_name like concat('%',#{username},'%')";
}
return sql;
}
}
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import com.darksnow.utils.ProviderUtil;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserMapper {
/*
type:填写处理的类的字节码文件对象
method:具体的处理方法
*/
@SelectProvider(type = ProviderUtil.class,method = "queryUsersBySexOrUsernameSQL")
@Results(value = {
@Result(column = "user_name",property = "userName")
})
List<User> queryUsersBySexOrUsername(@Param("sex") Integer sex,@Param("username") String username);
}
测试方法
@Test
public void queryUsersBySexOrUsername() {
SqlSession session = SqlSessionUtil.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.queryUsersBySexOrUsername(1,"");
System.out.println(users);
SqlSessionUtil.commitAndClose(session);
}
方式二
ProviderUtil
package com.darksnow.utils;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.jdbc.SQL;
public class ProviderUtil {
//如果用户名为空,那么只根据性别查询,如果用户名不为空,那么根据性别查询的同时,根据用户名进行模糊查询
public String queryUsersBySexOrUsernameSQL(@Param("sex") Integer sex, @Param("username") String username){
SQL sql = new SQL();
sql.SELECT("*").FROM("tb_user").WHERE("sex = #{sex}");
if (username != null && !"".equals(username)) {
sql.WHERE("user_name like concat('%',#{username},'%')");
}
return sql.toString();
}
}
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import com.darksnow.utils.ProviderUtil;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserMapper {
/*
type:填写处理的类的字节码文件对象
method:具体的处理方法
*/
@SelectProvider(type = ProviderUtil.class,method = "queryUsersBySexOrUsernameSQL")
@Results(value = {
@Result(column = "user_name",property = "userName")
})
List<User> queryUsersBySexOrUsername(@Param("sex") Integer sex,@Param("username") String username);
}
测试方法
@Test
public void queryUsersBySexOrUsername() {
SqlSession session = SqlSessionUtil.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.queryUsersBySexOrUsername(1,"坤");
System.out.println(users);
SqlSessionUtil.commitAndClose(session);
}
10.5 注解实现一对一
需求:查询订单编号20140921003的订单信息
接口
package com.darksnow.dao;
import com.darksnow.pojo.Order;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.*;
public interface OrderMapper {
@Select(value = "select * from tb_order where order_number = #{orderNumber}")
@Results(value = {
//id = true,表示id是主键
@Result(column = "id",property = "id",id = true),
@Result(column = "order_number",property = "orderNumber"),
/*
column = "user_id" user_id相当于一个外键,引入另外一方主键
property = "user" 关联对象的属性名,在Order类中有一个属性(User user),查询的结果封装到user
javaType = User.class 返回的对象类型,这里给的是字节码文件对象
one:一对一配置,使用注解@One中的select属性引入另一条sql语句所在的接口
*/
@Result(column = "user_id",property = "user",
javaType = User.class,one = @One(select = "com.darksnow.dao.UserMapper.findById"))
})
Order queryOneToOne(@Param("orderNumber") String orderNumber);
}
package com.darksnow.dao;
import com.darksnow.pojo.User;
import com.darksnow.utils.ProviderUtil;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserMapper {
@Select("select * from tb_user where id = #{id}")
User findById(@Param("id") Integer id);
}
测试方法
@Test
public void queryOneToOne(){
SqlSession session = SqlSessionUtil.getSession();
OrderMapper mapper = session.getMapper(OrderMapper.class);
Order order = mapper.queryOneToOne("20140921003");
System.out.println(order);
SqlSessionUtil.commitAndClose(session);
}
10.6 注解实现一对多
需求:查询id为1的用户及其订单信息
接口
package com.darksnow.dao;
import com.darksnow.pojo.User;
import com.darksnow.utils.ProviderUtil;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserMapper {
@Select("select * from tb_user where id = #{id}")
@Results({
@Result(column = "id",property = "id",id = true),
@Result(column = "user_name",property = "userName"),
@Result(column = "id",property = "orders",
javaType = List.class,many = @Many(select = "com.darksnow.dao.OrderMapper.findByUserId"))
})
User queryOneToMany(@Param("id") Integer id);
}
package com.darksnow.dao;
import com.darksnow.pojo.Order;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.*;
public interface OrderMapper {
@Select("select * from tb_order where user_id = #{userId}")
@Results({
@Result(column = "order_number",property = "orderNumber")
})
Order findByUserId(@Param("userId") Integer id);
}
测试方法
@Test
public void queryOneToMany(){
SqlSession session = SqlSessionUtil.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.queryOneToMany(1);
System.out.println(user);
SqlSessionUtil.commitAndClose(session);
}
PS:以后快面试的时候会去讲MyBatis的延迟加载,MyBatis的缓存
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现