mybatis框架介绍
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语句依赖于数据库,导致数据库移植性差,不能更换数据库。
- 框架还是比较简陋,功能尚有缺失。
mybaits框架整体架构
1.配置文件:
全局配置文件(核心配置文件):mybatis-confing.xml,作用:配置数据源(配置数据库连接信息),引入映射文件:XXXMapper.xml,作用:配置sql语句,参数,结果集封装类型等。
2.sqlsessionFactory:
作用:获取sqlsession
通过new SqlsessionFactoryBulider().build(inputStream)来构建,inputStream:读取配置文件的io流。
3.sqlsession:
作用:执行CRUD操作
4.Executor:
执行器,sqlsession通过调用它来完成具体的CRUD操作
5.mapper statement
在映射文件里面配置,包含3部分内容:
具体sql,sql执行所需要的参数类型,sql执行结果的封装类型
参数类型和结果集封装类型包括3种:
hashmap,基本数据类型和pojo
Mybatis的ORM方式
Object Relational Mapping 对象关系映射
mybatis的两种映射方式:
1.通过xml映射
2.通过注解
mybatis框架入门开发:
利用mybatis框架,从MySQL中查询所有的用户
数据库准备
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','女','盤丝洞');
select * from user;
创建maven工程,导入坐标
<!--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>
在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>
<!--mybatis环境的配置-->
<environments default="development">
<!--通常我们只需要配置一个就可以了, id是环境的名字 -->
<environment id="development">
<!--事务管理器:由JDBC来管理-->
<transactionManager type="JDBC"/>
<!--数据源的配置:mybatis自带的连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db4"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--加载映射文件,放到src下即可-->
<mapper resource="userMapper.xml"/>
</mappers>
</configuration>
在resources下创建映射文件userMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.czy.dao.UserMapper">
<!--
查询语句
resultType:返回的实体类的类型,类全名
-->
<select id="findAll" resultType="com.czy.pojo.User">
select * from user
</select>
</mapper>
创建UserMaper接口
package com.czy.dao;
import com.czy.pojo.User;
import java.util.List;
public interface UserMapper {
List<User> findAll();
}
将接口和映射文件绑定:
映射文件的namespace值 == 接口的全路径
映射文件的sql语句的id值 == 接口中的方法名
创建User类
package com.czy.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(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;
}
}
在mybatis中一个会话相当于一次访问数据库的过程,一个会话对象类似于一个connection连接对象。
1.SqlsessionFactoryBuilder:这是一个临时对象,用完就不需要了,通过这个工厂创建类来创建一个会话工厂。
2.SqlsessionFactory:从一个工厂类中得到一个会话对象,一个项目中只需要创建一个会话对象。通过会话工厂对象来创建会话对象。
3.Sqlsession:每次访问数据库都需要创建一个会话对象,这个会话对象不能共享。访问完成以后会话需要关闭。
package com.czy;
import com.czy.dao.UserMapper;
import com.czy.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class Test {
@org.junit.Test
public void test01() throws Exception {
//1.从配置文件中获取SqlsessionFactory
String resource = "mybatis-config.xml";
// //加载核心配置文件获取输入流
InputStream is= Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//2.从sqlsession中获取session
SqlSession sqlSession = sqlSessionFactory.openSession();
//3.使用sqlsession获取获取接口的动态代理对象 !!!!
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.findAll();
for (User user : userList) {
System.out.println(user);
}
//关闭会话
sqlSession.close();
}
}
结果:
编写流程:Resources工具类直接可以读取src目录下配置文件,转成输入流。
注意:
mybatis配置文件分两种:
1.核心配置文件:mybatis-config.xml配置连接数据库参数
2.映射文件:UserMapper.xml编写sql语句
mybatis执行流程分析
说明:
1.第一步:是从核心配置文件mybatis-config.xml中构建SqlSessionFactory对象,由于核心配置文件mybatis-config.xml中关联了映射文件UserMapper.xml,所以在SqlSessionFactory中也存在映射文件的内容
2.第二步:是从SqlSessionFactory中获取SqlSession会话对象,其实SqlSession会话对象底层封装的就是conn连接对象
3.第三步:是通过SqlSession会话对象调用查询方法selectList然后根据参数找到映射文件中中的sql语句并将数据封装到pojo的User对象中
核心配置文件 mybatis-config.xml
可以在idea中定义模板。
<?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 resource="db.properties"></properties>
<!--settings-->
<settings>
<!--开启驼峰自动映射-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!--别名-->
<typeAliases>
<package name="com.czy.sh.pojo"></package>
</typeAliases>
<!--mybatis环境的配置
一个核心配置文件,可以配置多个运行环境,default默认使用哪个运行环境
-->
<environments default="development">
<!--通常我们只需要配置一个就可以了, id是环境的名字 -->
<environment id="development">
<!--事务管理器:由JDBC来管理-->
<!--
事务管理器type的取值:
1. JDBC:由JDBC进行事务的管理
2. MANAGED:事务由容器来管理,后期学习Spring框架的时候,所有的事务由容器管理
-->
<transactionManager type="JDBC"/>
<!--数据源的配置:mybatis自带的连接池-->
<!--
数据源:
1. POOLED:使用mybatis创建的连接池
2. UNPOOLED:不使用连接池,每次自己创建连接
3. JNDI:由服务器提供连接池的资源,我们通过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>
<!--加载其它的映射文件 注:注解开发是点号-->
<!-- <package name="com.czy.sh.dao"></package>-->
<!--加载其它的映射文件 注:不是点号-->
<!--<mapper resource="org/mybatis/example/BlogMapper.xml"/>-->
<!--
加载其它的映射文件 xml形式
包扫描方式加载mapper映射文件,说明:
1. 要求mapper映射文件,与mapper接口要放在同一个目录
2. 要求mapper映射文件的名称,与mapper接口的名称要一致
-->
<package name="com.czy.sh.dao"></package>
</mappers>
</configuration>
mybatis核心配置
mybatis-config.xml,是MyBatis的全局配置文件,包含全局配置信息,如数据库连接参数、插件等。整个框架中只需要一个即可。
1.mybatis全局配置文件是mybatis框架的核心配置,整个框架只需一个;
2.mybatis全局配置文件的配置顺序,必须按照一下顺序进行配置:
properties:属性配置
settins:设置
typeAliases:类型别名设置
typeHandlers:类型处理器
enviroments:环境配置
environment:环境配置
transactionManager:事务管理器
dataSource:数据源
mappers:映射器
properties(属性)
作用:
1.加载外部的java资源文件(properties文件,一般是数据源的配置);
2.通过子标签property设置属性
需求:
使用properties属性,配置数据库连接参数
property标签设置
1.通过property标签设置属性;
2.使用¥{key}获取设置的属性值
新建jdbc.properties文件,将连接参数抽取到配置文件中:
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/heima
username=root
password=root
引入资源文件
【注意事项】:子标签和属性resource同时加载一个变量,先加载子标签,然后在加载resource关联的外部文件的值,那么子标签中的内容会被覆盖。此时子标签存在就没有意义了。
settings(设置)
settings参数有很多,经常用到的是mapUnderscoreToCamelCase,就是映射下划线到驼峰命名。
开启驼峰匹配:完成经典的数据库命名到java属性的映射
经典数据库命名:如果多个单词,通常用下划线分割。
java:第二个单词大写。
驼峰匹配:相当于去掉数据库中的下划线和java进行匹配。
应用场景
从数据库中查到数据的字段名user_name和实体类属性名userName,不一致,导致查到的数据无法封装到实体类中。
但是,实体类中的属性userName符合驼峰命名,数据库字段名user_name符合数据库经典字段命名。
如果此时直接查询数据,那么用户名是null。
解决方案
1.配置驼峰匹配
在mybatis-config.xml中做如下配置:
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
2.如果数据库字段名和属性名不一致,或者是也不满足上面的命名规则。可以通过在SQL语句中为字段名取别名进行映射。
<select id = "queryAll" resultType = "com.czy.pojo.User">
select *,user_name as userName from user
</select >
typeAliases(类型别名)
作用
类型别名是给类的权限定名称取一个段名称。存在的意义就在于减少类完全限定名称的长度。
这些全限定类名可通过设置类型别名–短名称代替。类型别名的设置有以下几种方式:
package
扫面指定包下的所有类,扫描之后的别名就是类名,不区分大小写:
<!--
三、typeAliases(类型别名)
【1】作用:给类的全限定名称 取一个短名称 com.heima.mybatis.pojo.User==>User
【2】用法:
1、单独取别名:<typeAlias type="com.heima.mybatis.pojo.User" alias="User"/>
2、批量取别名:<package name="com.heima.mybatis.pojo"/> 扫描到当前包下的所有类
类的类名==》别名
-->
<typeAliases>
<!--扫描com.itheima.sh.pojo包下所有的类,类名直接作为别名(别名不区分大小写)-->
<package name="com.itheima.sh.pojo"/>
</typeAliases>
使用别名:
内置别名
这是一些为常见的 Java 类型内建的相应的类型别名。它们都是不区分大小写的,注意对基本类型名称重复采取的特殊命名风格。
别名 | 映射的类型 |
---|---|
_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 |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
typeHandlers(类型处理器)
无论是mybatis在预处理语句(PreparedStatement) 中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成java类型,下表描述了一些默认的类型处理器
数据库数据类型:varchar ===StringTypeHandler===> 实体类:String
数据库数据类型:double ===DoubleTypeHandler===> 实体类中的数据: java.lang.Double
environments
mybatis可以配置成适应多种环境,例如:开发、测试和成产需要有不同的配置:
默认环境设置
第一步:在environments标签中配置多个environment,通过属性default指定一个默认环境配置:
第二步:在构建SqlSessionFactory时,可指定具体环境,如果不指定就使用默认的环境配置;
指定环境设置
第一步:在environments中设置多个环境:
第二步:在构建SqlSessionFactory时,通过environment的id指定环境
注意事项:
虽然,这种方式也可以做到很方便的分离多个环境,但是实际使用场景下,我们更多的是选择使用第三方的连接池:druid,C3P0。并且使用spring来管理数据源(连接池),来做到环境的分离。
mappers(映射器)
mappers: UserMapper.xml =====>UserMapper.java接口关联
作用:维护接口和映射文件之间的关系。
方式一:加载映射文件方式resource
在mybatis-config.xml文件中,通过mapper标签的resource属性引入当前工程src下的映射文件:
缺点:有多个映射文件就是配置多个mapper,很麻烦。
方式二:加载接口,关联映射文件方式package
在mybatis-config.xml文件中开启包扫描:扫描包下的所有接口和对应的映射文件。
条件:
1.接口名和映射文件名保持一致
2.路径保持一致
在mybatis-config.xml配置mapper接口的全路径:
<mappers>
<package name="com.itheima.sh.dao"/>
</mappers>
原理
扫描目标目录下的mapper接口,并按照class的方式找到接口对应的映射文件
要求:
1.映射文件和mapper接口在同一目录下
2.文件名必须一致
3.映射文件的namespace必须和mapper接口的全路径保持一致
缺点
1、需要遵守的条件太多
2、mapper.xml和mapper.java没有分离。
编写会话工具类
- 在静态代码块中创建会话工厂对象
- 编写静态方法得到会话对象,设置自动提交事务
- 编写静态方法得到会话对象,方法接收调用者传递的布尔类型,决定是否自动提交事务
- 编写静态方法接收会话对象,手动提交事务并且关闭会话
- 编写静态方法接收会话对象,回滚事务并且关闭会话
package com.czy.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 sqlSessionFactory;
static {
//实例化工厂建造类
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//读取核心配置文件
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//创建工厂对象
sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取会话对象,自动提交事务
* @return
*/
public static SqlSession getSession(){
return sqlSessionFactory.openSession(true);
}
/**
* 是否自动提交事务
* @param flag
* @return
*/
public static SqlSession getSession(Boolean flag){
return sqlSessionFactory.openSession(flag);
}
/**
* 提交事务并关闭session
*/
public static void commitAndClose(SqlSession session){
if (session!=null){
session.commit();
session.close();
}
}
/**
* 回滚事务,并关闭session
*/
public static void rollBackAndClose(SqlSession session){
if (session!=null){
session.rollback();
session.close();
}
}
}
测试:
public class Test {
@org.junit.Test
public void test01() throws Exception {
SqlSession session = SqlSessionUtil.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> all = mapper.findAll();
for (User user : all) {
System.out.println(user);
}
SqlSessionUtil.commitAndClose(session);
}
}
mybatis映射文件配置
mapper映射文件中定义了操作数据库的sql,每一个sql都被包含在一个statement中。映射文件是mybatis操作数据库的核心。
sql语句-CRUD
映射文件需要直接书写sql语句对数据库进行操作,对数据库操作sql语句主要有CRUD这四类,这四类对应到映射文件中的配置为四类标签:select,insert,update,delete
public interface UserMapper {
List<User> findAll();
User findById(int id);
void save(User user);
void update(User user);
void delete(int id);
}
select
Select标签:用来编写查询语句的statement
UserMapper.xml:
<!--根据id查询用户数据-->
<!--parameterType="int" 表示sql语句参数id的类型,int是Integer的别名-->
<select id="queryById" resultType="user" parameterType="int">
select * from user where id = #{id}
</select>
insert
Insert标签:编写新增语句的statement
<!--新增
注意:#{username},#{birthday},#{sex},#{address} 大括号里面的值必须和pojo的实体类User类中的属性名一致,否则会报错。其实这里看的是User类中的getXxx()的get后面的xxx内容
例如 getUserName---看的是userName ,就是将get去掉,U变为小写u,后面不变
-->
<insert id = "save">
insert into user values(null,#{username},#{birthday},#{sex},#{address})
</insert>
update
Update标签:编写更新语句的statement
<!--
username = #{username}
1.等号左边表示数据表列名
2.#{username} 这里的username需要和pojo中的get和set方法后面的标识符一致
-->
<update id="updateUser">
update user set username = #{username},birthday=#{birthday},sex=#{sex},address=#{address} where id = #{id}
</update>
delete
Delete标签:
<delete id = "delete">
delete from user where id =#{id}
</delete>
传入的参数
parameterType
CRUD标签都有一个属性parameterType,底层的statement通过它指定接收的参数类型,入参数据有以下几种类型:hashMap,基本数据类型(包装类),实体类;
在mybatis中入参的数据类型分为2种:
1.简单数据类型:int,String,long,date
2.复杂数据类型:类和map
说明:如果传递参数是数组或者集合,底层都会封装到Map集合中。
【基本类型数据】
<!--根据id查询-->
<!--parameterType="int" 表示sql语句参数id的类型,int是Integer的别名.MyBatis 可以通过类型处理器(TypeHandler) 根据接口中的方法User queryById(Integer id)参数类型推断出具体传入语句的参数类型。-->
<select id="queryById" resultType="user" parameterType="int">
select * from user where id = #{id}
</select>
【pojo类型】
<insert id="savetUser" parameterType="User">
INSERT INTO user(...)
</insert>
说明:对于parameterType属性可以不写,如果不写,mybatis会通过TypeHandler根据接口中的方法参数类型推断出具体传入语句的参数类型
自增主键
需求
新增一条数据,将这条数据的主键封装到实体类中,并查看主键的值。
测试类代码:
@Test
public void saveUser() throws Exception {
User user = new User();
user.setUsername("蔡徐坤");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("上海");
userMapper.saveUser(user);
//查看新的数据的主键值
System.out.println(user.getId());//null
}
直接获取的结果是null。
有两种方式可以实现添加好数据之间将主键封装到实体类对象中:
实现一:使用insert标签的子标签selectKey实现;
mysql中的函数:last_insert_id() 得到最后添加的主键
<insert id="save" parameter="user">
insert into user values(null,#{username},#{birthday},#{sex},#{address})
<!--
keyColumn:主键在表中对应的列名
keyProperty:主键在实体类中对应的属性名
resultType:主键的数据类型
order:
BEFORE: 在添加语句前执行查询主键的语句
AFTER: 在添加语句后执行查询主键的语句
-->
<selectKey keyColumn = "id" keyProperty ="id" resultType = "int" order = "AFTER">
select last_insert_id();
</selectKey>
</insert>
实现二:使用insert标签的属性useGeneratedKeys,keyProperty,keyColumn实现;
<!-- useGeneratedKeys :true 获取自动生成的主键,相当于select last_insert_id()
keyColumn:表中主键的列名
keyProperty:实体类中主键的属性名
-->
<insert id="save2" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into user values(null,#{username},#{birthday},#{sex},#{address})
</insert>
单个参数,多个参数
主要针对简单类型的数据(int,string,long,date)等数据进行入参处理。
单个参数
接口方法传入一个参数
接口参数
User queryById(Integer id);
接收参数
1.通过#{参数名}接收
<!--根据id查询-->
<select id="queryById" resultType="User" parameterType="int">
select *,user_name AS userName from user where id = #{id}
</select>
2. 通过#{任意变量名}接收
<!--根据id查询-->
<select id="queryById" resultType="User" parameterType="int">
select *,user_name AS userName from user where id = #{abc}
</select>
结论
当接口方法传入一个参数时,mybatis不做特殊处理,只需要#{任意变量名}即可
多个参数
需求:根据用户名和性别查询用户
接口参数
//根据用户名和性别查询
User queryByUserNameAndSex(String userName, String sex);
###userMapper.xml
<select id="queryByUserNameAndSex" resultType="User">
select * from user where username=#{username} and sex=#{sex}
</select>
测试类
@Test
public void queryByUserNameAndSex() throws Exception {
User user = userMapper.queryByUserNameAndSex("孙悟空", "男");
System.out.println("user = " + user);
}
当传入多个参数时,mybatis底层进行了处理,我们需要按照一下几种方式解决异常:
1.使用参数索引获取:arg0,arg1
<select id ="queryByUserNameAndSex" resultType = "user">
select * from user where username = #{arg0} and sex = #{arg1}
</select>
说明:
这里的sql语句的参数书写顺序:select * from user where username=#{arg0} and sex=#{arg1}
和User user = userMapper.queryByUserNameAndSex(“孙悟空”, “男”); 传递实参顺序一致。
也就是说:username=#{arg0} —》arg0的值是姓名孙悟空。
sex=#{arg1}—》arg1的值是性别男。
2.使用参数位置获取:param1,param2
<!--根据用户名和性别查询-->
<select id="queryByUserNameAndSex" resultType="User">
select * from user where username=#{param1} and sex=#{param2}
</select>
3.使用命名参数获取,明确指定传入参数的名称:
步骤一:在接口中传入参数时通过@Param指定参数名称
User queryByUserNameAndSex(@Param("username") String username,@Param("sex") String sex);
步骤二:在接收参数时,通过指定的名称获取参数值
<!--根据用户名和性别查询-->
<select id="queryByUserNameAndSex" resultType="User">
<!--#{username}这里的username是@Param("username"),也就是说@Param("标识符")标识符是什么,这里就写什么-->
select * from user where username=#{username} and sex=#{sex}
</select>
pojo参数
使用pojo来传递参数
void saveUser(User user);
<!--新增-->
<insert id="saveUser">
insert into user values (null ,#{username},#{birthday},#{sex},#{address})
</insert>
说明:接口方法传入pojo类型的数据时,mybatis底层直接使用pojo封装数据,sql语句中#{username}取值就是到pojo中调用getUsername();
HashMap参数
需求:模拟用户登录,登录方法参数是Map集合,泛型都是String类型分别表示用户名和性别。
在UserMapper接口中添加以下方法:
/**
* 用户登陆,参数为map
* @return
*/
User login(Map<String,String> map);
UserMapper.xml配置文件:
<!--
将map的key作为参数名称来传递参数
-->
<select id="login" resultType="User">
select * from user where username=#{username} and sex=#{sex}
</select>
参数值的获取
参数值的获取指的是,statement获取接口方法中传入的参数,获取参数,有两种方式:#{}和${}
#{}和${}取值
#{}取值:
mybatis后台处理:
${}取值:
mybits后台处理:
注意:${id} 获取id值时,必须使用命名参数取值:
#{}取值和${}取值的小结
1.sql语句中获取参数的方式:
#{}: select * from user where id = ?
${}: select * from user where id = 1
2. 取值的相同点:
都能获取接口方法传入的参数值
3.取值的不同点:
#{}取值:是以预编译的形式将参数设置到sql语句中。防止sql的注入问题
${}取值:直接把获取到的参数值,拼接到sql语句中,会有安全问题,不能防止sql注入
$()取值的应用场景:
1、企业开发中随着数据量的增大,往往会将数据表按照年份进行分表,如:2017_user,2018_user....,对这些表进行查询就需要动态把年份传入进来,而年份是表名的一部分,并不是参数,JDBC无法对其预编译,所以只能使用${}进行拼接:
SELECT * FROM ${year}_user;
2、根据表名查询数据总记录数:
SELECT COUNT(*) FROM user
SELECT COUNT(*) FROM order
SELECT COUNT(*) FROM ${tableName}
简言之:如果需要设置到SQL中的不是查询的条件,只能使用${}拼接;
结果映射
在使用原生的JDBC操作时,对于结果集resultSet,需要手动处理,mybatis框架提供了resultType和resultMap来对结果集进行封装。
注意:只要一个方法有返回值需要处理,那么resultType和resultMap必须有一个。
resultType
从sql语句中返回期望类型的类的完全限定名或别名。如果返回的是集合,那么应该设置集合中包含的类型,而不是集合本身。可以使用resultType或resultMap,但不能同时使用。
返回值是简单类型
返回值是一个pojo对象时
返回值是一个集合List
返回值是map
1.返回一条数据,封装到map中
需求:
查询id是1的数据,将查询的数据结果封装到Map<String,Object>中
接口:
//需求:查询id是1的数据,将查询的结果封装到Map<String,Object>中
Map<String,Object> selectByIdReturnMap(Integer id);
sql:
<select id="selectByIdReturnMap" resultType="map">
select * from user where id=#{id}
</select>
结果:
map = {birthday=1980-10-24, address=花果山水帘洞, sex=男, id=1, username=孙悟空}
通过上面的测试,我们可以看到,如果返回一条数据放到Map中,那么列明会作为Map集合中的key,结果作为map中的value。
返回多条数据封装到map中
需求:
查询数据表所有的数据封装到Map<String,User>集合中
要求key值为一条记录的主键,value值为pojo对象
接口:在接口上面通过@MapKey指定key值封装到列数据
@MapKey("id")
Map<Integer,Object> selectUserByIdReturnMap2();
sql
<select id="selectUserByIdReturnMap2" resultType="map">
select * from user
</select>
结果:
{
1={birthday=1980-10-24, address=花果山水帘洞, sex=男, id=1, username=孙悟空},
2={birthday=1992-11-12, address=白虎岭白骨洞, sex=女, id=2, username=白骨精},
3={birthday=1983-05-20, address=福临山云栈洞, sex=男, id=3, username=猪八戒},
4={birthday=1995-03-22, address=盤丝洞, sex=女, id=4, username=蜘蛛精},
5={birthday=2022-06-14, address=河南, sex=男, id=5, username=张三},
6={birthday=2022-06-14, address=河南, sex=女, id=6, username=李四},
7={birthday=2022-06-14, address=河南, sex=女, id=7, username=王五}
}
resultMap
ResultMap是mybatis中最重要最强大的元素,使用ResultMap可以解决两大问题:
- POJO属性名和表结构字段名不一致的问题(有些情况下也不是标准的驼峰格式,比如id和userId)
- 完成高级查询,比如说,一对一、一对多、多对多。(后面多表中会涉及到)
查询数据的时候,查不到userName的信息,原因:数据库的字段名是user_name,而POJO中的属性名字是userName,两端不一致,造成mybatis无法填充对应的字段信息。
解决方案一:在sql语句中使用别名
<select id="queryById" resultType="user" parameterType="int">
select *,name as username from user where id = #{id}
</select>
解决方案二:在mybatis-config.xml配置文件中设置驼峰匹配
<settings>
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
注意:这种方式只能解决列名是下划线命名。
解决方案三:resultMap自定义
步骤一:将驼峰匹配注释掉
步骤二:配置resultMap
在映射文件中自定义结果集类型:
<!--type 表示结果集封装的类型
autoMapping属性的值:
为true时:在字段和属性名称相同时,会进行自动映射。如果不配置,则默认为true。
为false时:只针对resultMap中已经配置的字段作映射。
-->
<resultMap id="userResultMap" type="user" autoMapping="true">
<!--配置主键映射关系-->
<id column="id" property="id"></id>
<!--配置用户名的映射关系 column 表示数据表列 property表示pojo的属性-->
<result column="username" property="username" ></result>
</resultMap>
步骤三:修改查询语句
<select id="findAll" resultMap="userResultMap">
select * from user
</select>
动态sql
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
例如:
需求:查询男性用户,如果输入了用户名,按用户名模糊查询,如果没有输入用户名,就查询所有男性用户。
正常的sql语句:查询男性并且用户名中包含zhang
select * from tb_user where sex = "男" and user_name like '%zhang%'
或
select * from tb_user where sex = "男"
实现需求时还要判断用户是否输入用户名来做不同的查询要求,而这里似乎没有办法判断是否输入了用户名,因此可以考虑使用动态sql来完成这个功能。
if标签
格式:
<if test="判断条件">
满足条件执行的代码
</if>
说明:
1)if标签:判断语句,用于进行逻辑判断的。如果判断条件为true,则执行if标签的文本内容
2)test属性:用来编写表达式,支持ognl;
接口:
/**
* 根据用户名模糊查询
* @param userName
* @return
*/
List<User> queryLikeUserName(@Param("userName") String userName);
sql:
<select id="queryLikeUserName" resultType="user">
select * from user where sex='男'
<if test="userName!=null and userName.trim()!=''">
and username like '%${userName}%'
</if>
</select>
注意:<if>
判断中:
1、if标签:用来判断;
2、test属性:使用OGNL表达式,完成具体的判断业务逻辑;
3、这里使用的字符串拼接,所以这里不能是#取值,只能使用$取值,否则会报错
测试:
有名字
没有名字
choose,when,otherwise
choose标签:分支选择(多选一,遇到成立的条件即停止)
when子标签:编写条件,不管有多少个when条件,一旦其中一个条件成立,后面的when条件都不执行。
test属性:编写ognl表达式
otherwise子标签:当所有条件都不满足时,才会执行该条件
需求:
编写一个查询方法,设置两个参数,一个是用户名,一个是住址。
根据用户名或者住址查询所有男性用户:
如果输入了用户名则按照用户名模糊查找,
否则就按照住址查找,两个条件只能成立一个,
如果都不输入就查找用户名为“孙悟空”的用户。
接口:
/*
查询用户名或者地址
*/
List<User> queryByUserNameOrAddress(@Param("userName") String userName, @Param("address") String address);
sql
<select id="queryByUserNameOrAddress" resultType="user">
select * from user where sex='男'
<choose>
<when test="userName !=null and userName.trim() != ''">
and username like '${userName}%'
</when>
<when test="address !=null and address.trim() != ''">
and address = #{address}
</when>
<otherwise>
and username = '孙悟空'
</otherwise>
</choose>
</select>
where
where标签:拼接多条件查询时 1、能够添加where关键字; 2、能够去除多余的and或者or关键字
案例:按照如下条件查询所有用户,
如果输入了用户名按照用户名进行查询,
如果输入住址,按住址进行查询,
如果两者都输入,两个条件都要成立。
接口
List<User> queryByUserNameAndAge(@Param("userName") String userName, @Param("address") String address);
sql
<select id="queryByUserNameAndAge" resultType="user">
SELECT * FROM user
<where>
<if test="userName != null and userName.trim()!=''">
username = #{userName}
</if>
<if test="address!=null and address.trim()!=''">
AND address = #{address}
</if>
</where>
</select>
set
set标签:在update语句中,可以自动添加一个set关键字,并且会将动态sql最后多余的逗号去除。
案例:修改用户信息,如果参数user中的某个属性为null,则不修改。
如果在正常编写更新语句时,如下:
update user SET username = ?, birthday=?, sex=?, where id = ?
那么一旦在传递的参数中没有address,此时生成的sql语句就会因为多了一个逗号而报错。
接口
void updateSelectiveUser(User user);
sql
<!--选择性地对user数据进行修改-->
<update id="updateSelectiveUser">
update user
<set>
<if test="username != null and username.trim()!=''">
username = #{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.setUsername("锁哥1");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("");
user.setId(7);
userMapper.updateSelectiveUser(user);
}
结果
update user SET username = ?, birthday=?, sex=? where id = ?
foreach
foreach标签:遍历集合或者数组
<foreach collection="集合名或者数组名" item="元素" separator="标签分隔符" open="以什么开始" close="以什么结束">
#{元素}
</foreach>
collection属性:接收的集合或者数组,集合名或者数组名
item属性:集合或者数组参数中的每一个元素
separator属性:标签分隔符
open属性:以什么开始
close属性:以什么结束
需求:按照id值是1,2,3来查询用户数据;
接口:
List<User> queryByIds(@Param("arrIds") Integer[] arrIds);
这里一定加@Param(“arrIds”),否则报错
sql
<!--根据多个id值查询-->
<select id="queryByIds" resultType="user">
SELECT * FROM user WHERE id IN
<foreach collection="arrIds" item="ID" separator="," open="(" close=")">
#{ID}
</foreach>
</select>
mybatis的高级查询
sql准备:
/*
Navicat MySQL Data Transfer
Source Server : localhost
Source Server Version : 50622
Source Host : localhost:3306
Source Database : heima81
Target Server Type : MYSQL
Target Server Version : 50622
File Encoding : 65001
Date: 2019-08-20 17:08:12
*/
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_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');
-- ----------------------------
-- 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');
实体类
Item:
package com.czy.pojo;
public class Item {
private int id;
private String itemName;
private double itemPrice;
private String itemDetail;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public double getItemPrice() {
return itemPrice;
}
public void setItemPrice(double itemPrice) {
this.itemPrice = itemPrice;
}
public String getItemDetail() {
return itemDetail;
}
public void setItemDetail(String itemDetail) {
this.itemDetail = itemDetail;
}
@Override
public String toString() {
return "Item{" +
"id=" + id +
", itemName='" + itemName + '\'' +
", itemPrice=" + itemPrice +
", itemDetail='" + itemDetail + '\'' +
'}';
}
}
order
package com.czy.pojo;
public class Order {
private Integer id;
private String orderNumber;
//关联User对象
private User user;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getOrderNumber() {
return orderNumber;
}
public void setOrderNumber(String orderNumber) {
this.orderNumber = orderNumber;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String toString() {
return "Order{" +
"id=" + id +
", orderNumber='" + orderNumber + '\'' +
", user=" + user +
'}';
}
}
orderDetail
package com.czy.pojo;
public class OrderDetail {
private int id;
private int orderId;
private int itemId;
private double totalPrice;
private String status;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getOrderId() {
return orderId;
}
public void setOrderId(int orderId) {
this.orderId = orderId;
}
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public double getTotalPrice() {
return totalPrice;
}
public void setTotalPrice(double totalPrice) {
this.totalPrice = totalPrice;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
@Override
public String toString() {
return "OrderDetail{" +
"id=" + id +
", orderId=" + orderId +
", itemId=" + itemId +
", totalPrice=" + totalPrice +
", status='" + status + '\'' +
'}';
}
}
user
package com.czy.pojo;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
public class User implements Serializable {
private Long id;
// 用户名
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
//0 女性 1 男性
private Integer sex;
//订单
List<Order> orders;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public List<Order> getOrders() {
return orders;
}
public void setOrders(List<Order> orders) {
this.orders = orders;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", password='" + password + '\'' +
", name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
", orders=" + orders +
'}';
}
}
测试接口:
public interface UserMapper {
//完成根据id查询用户数据;
User selectById(Long id);
}
sql:
<!--根据id查询:statement-->
<select id="selectById" resultType="User">
SELECT * FROM tb_user WHERE id=#{id}
</select>
表之间关系介绍
1.tb_user和 tb_order表关系
tb_user 《==》 tb_order:一对多, 一个人可以下多个订单
tb_order 《==》 tb_user:一对一,一个订单只能属于一个人
结论:tb_user和tb_order属于一对多的关系,需要将一方tb_user的主键作为多方tb_order的外键维护关系
2.tb_order 和 tb_item 表关系
tb_order 《==》 tb_item :一个订单可以有多个商品
tb_item 《==》 tb_order:一个商品可以在多个订单上
结论:tb_order和tb_item属于多对多的关系,需要创建中间表tb_orderdetail维护两个表的关系,并且将两张表 的主键作为中间表的外键
一对一查询
需求:通过订单编号20140921003查询出订单信息,并查询出下单人信息。
订单接口:
<select id="queryOrderAndUserByOrderNumber2" resultType="order">
select * from tb_order as order inner join tb_user as user on order.user_id = user.id where user.id = #{orderNumber}
</select>
注意:需要在order实体类中添加User属性
mybatis-config.xml配置文件中将orderMapper.xml添加到映射器中
sql:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
映射文件
namespace 指定接口的类全名
-->
<mapper namespace="com.itheima.sh.dao.OrderMapper">
<!--
1.autoMapping="true" 表示只需要给当前表的id然后自动映射当前表的其他列值到
对应实体类的属性中,这属于偷懒行为,开发中我们最好都书写出来
2.id标签表示id的映射关系
3.result标签表示其他列和pojo类的属性映射关系
4.一对一映射关系使用子标签association来表示引用的另一个pojo类的对象
-->
<resultMap id="orderAndUserResultRelative" type="Order" autoMapping="true">
<!--主表主键-->
<id column="id" property="id"/>
<!--关联关系-->
<!--
1.property="user" 表示在Order类中的引用的User类的对象成员变量名
2.javaType="User" 表示引用的user对象属于User类型
-->
<association property="user" javaType="User" autoMapping="true">
<!--从表主键-->
<id column="id" property="id"/>
<!--<result column="user_name" property="userName"/>-->
</association>
</resultMap>
<!--多表关联查询:一对一-->
<select id="queryOrderAndUserByOrderNumber2" resultMap="orderAndUserResultRelative">
SELECT
*
FROM
tb_order tbo
INNER JOIN tb_user tbu ON tbo.user_id = tbu.id
WHERE
tbo.order_number = #{orderNumber}
</select>
</mapper>
结果
注意:
因为tb_user表的主键是id,tb_order的主键也是id。查询的结果中有两列相同的id字段。在将查询结果封装到实体类的过程中就会封装错误。
解决方案:
1、建议将所要查询的所有字段显示地写出来;
2、将多表关联查询结果中,相同的字段名取不同的别名;
resultMap中应该如下配置:
一对多查询
需求:查询id为1的用户及其订单信息
分析:
一个用户可以有多个订单
用户(1) ---- 订单(n)
需要在user实体类中添加集合型的订单
接口:
/**
* 根据用户id查询用户及其订单信息
* @param id
* @return
*/
User oneToManyQuery(@Param("id") Long id);
sql
<!--自定义结果集-->
<resultMap id="oneToManyResult" type="User" autoMapping="true">
<!--User的主键-->
<id column="uid" property="id"/>
<!--Order关联映射-->
<!--
1.一对多使用collection子标签进行关联多方Order
2.属性:
1)property="orders" 这里的orders表示User类的成员变量orders
2)javaType="List" 表示User类的成员变量orders存储的Order对象使用的类型,这里是List,可以不配置
3) ofType="Order" 表示List集合中存储数据的类型 Order
-->
<collection property="orders" javaType="List" ofType="Order" autoMapping="true">
<!--Order的主键-->
<id column="oid" property="id" />
</collection>
</resultMap>
<!--根据用户ID查询用户及其订单数据-->
<select id="oneToManyQuery" resultMap="oneToManyResult">
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_user tbu
INNER JOIN tb_order tbo ON tbu.id = tbo.user_id
WHERE
tbu.id = #{id}
</select>