上篇文章我们说了MyBatis基础-----入门篇,讲解了MyBatis框架入门。CRUD案例以及基础用法等内容。这篇我们继续进行学习,MyBatis进阶----进阶篇。
回忆一下使用MyBatis的步骤:
- 在pom.xml中引入相关jar包
- 创建全局配置文件,包含数据库参数
- 创建实体类,数据库对应表
- 编写对应mapper文件
- 加载核心配置文件mybatis-config.xml
- 创建SqlSessionFactoryBuilder对象,通过.build方法创建会话工厂,读取流
- sqlSessionFactory.openSession创建sqlSession对象,执行sql语句。
(5-7步骤:
Resources.getResourceAsStream(“mybatis-config.xml”);加载核心配置文件得到InputStream------>
通过SqlSessionFactoryBuilder------>
创建sqlSessionFactoryBuilder对象------>
通过.build()读取InputStream创建sqlsessionFactory会话工厂------->
sqlSessionFactory.openSession();创建sqlSession对象------>
调用对应的方法执行sql语句。)
private SqlSessionFactory sqlSessionFactory;
@Before
public void before() throws IOException {
//加载核心配置文件mybatis-config.xml
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//创建sqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//读取配置文件的流,创建sqlSessionFactory会话工厂
sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
}
在上一次学习中,我们发现在使用mybatis通过session调用方法进行增删改查时,会因为sql标识符是字符串类型,且参数是object类型,例如在调用select方法时
这是sql的调用方无法确定sql标识符是否正确,也无法确定应该传递什么样的参数,从而影响了开发效率,易出错。
MyBatis如何解决?引出今天要写的内容
Mapper代理
直接利用session+id来执行sql的方式存在一些问题
- session执行sql时都需要提供要执行sql的id,而这个id是字符串类型,意味着id是否正确在编译期间是无法获知的,必须等到运行时才能发现错误,
- sql需要的参数和返回值类都不明确,这也增加了出错的概率
理想的状况:
像调用方法一样调用sql,既避免了直接写id的问题,也可以明确指定方法的参数类型和返回值类型
解决方案:
MyBatis通过动态代理来解决, 动态代理的意思就是自动产生一个实现类。
简单的说动态代理(动态生成),就是在运行过程中自动产生一个对象,用它来代理原本已经存在的对象的方法
。我们获得的这个mapper就是一个代理对象
MyBatis中本来由Executor(被代理对象)来完成sql的执行,现在由代理对象(自动生成)来代理Executor完成,代理对象会将我们的操作转交给Executor,由mapper代理去与session进行交互,同时屏蔽了容易出错的地方。
问题是:MyBatis怎么知道代理对象是什么样的对象呢?
这就需要为MyBatis提供Mapper接口,这个接口就是对mapper.xml中的sql语句的声明,与DAO层的接口一样,通过接口,我们可以明确的告诉sql的调用者,返回值类型,方法名称,参数分别是什么。
MyBatis其实就是获取执行的方法名称作为id来查找sql的,所以方法名和id必须完全一致
使用步骤
- 创建接口类
package com.cx.mapper;
import com.cx.pojo.Product;
import java.util.List;
public interface ProductMapper {
//根据id查询
Product selectById(int pid);
//根据name实现模糊查询
List<Product> selectByName(String pname);
}
- 提供响应的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">
<mapper namespace="com.cx.mapper.ProductMapper">
<!--根据id进行查询-->
<select id="selectById" parameterType="int" resultType="com.cx.pojo.Product">
select * from product where pid = #{pid}
</select>
<!--根据name进行模糊查询-->
<select id="selectByName" parameterType="String" resultType="com.cx.pojo.Product">
select * from product where pname like '%${新疆}%'
</select>
</mapper>
- 获取代理对象 执行方法完成操作
import com.cx.mapper.ProductMapper;
import com.cx.pojo.Product;
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.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyTest2 {
private SqlSessionFactory sqlSessionFactory;
@Before
public void before() throws IOException {
//加载核心配置文件mybatis-config.xml
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//创建sqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//读取配置文件的流,创建sqlSessionFactory会话工厂
sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
}
@Test
public void test1(){
SqlSession sqlSession = sqlSessionFactory.openSession();
ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
Product product = mapper.selectById(4);
System.out.println(product);
sqlSession.close();
}
@Test
public void test2(){
SqlSession sqlSession = sqlSessionFactory.openSession();
ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
List<Product> product = mapper.selectByName("新疆");
System.out.println(product);
sqlSession.close();
}
}
使用mapper代理注意事项
- 必须保证mapper.xml中的namespace与接口的全限定名称一致
- 方法的名称必须与对应的sql statement的id一致
- 方法的参数必须与对应的sql statement的parameterType一致
- 方法的返回值必须与对应的sql statement的resultType一致
案例总结如下:
mapper代理实现步骤:
- 创建接口,接口中的方法与sql对应
- mapper文件中的namespace指定对应的接口类
- 通过sqlsession.getMapper()来获取代理对象
XML配置
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
>configuration(配置)
>>properties(属性)
>>settings(设置)
>>typeAliases(类型别名)
>>typeHandlers(类型处理器)
>>objectFactory(对象工厂)
>>plugins(插件)
>>environments(环境配置)
>>>environment(环境变量)
>>>transactionManager(事务管理器)
>>>dataSource(数据源)
>>databaseIdProvider(数据库厂商标识)
>>mappers(映射器)
注意:>
代表层次结构,我们在书写的时候一档要遵循上面的结构偶进行,否则就会报错。
单独使用MyBatis的场景是很少的,后续都会将其与Spring进行整合,并由Spring来对MyBatis进行配置,下面讲解的为需要重点关注,其余的了解即可,若遇到特殊需求可查阅官方文档
属性(properties)
properties可从配置文件或是properties标签中读取需要的参数,使得配置文件各个部分更加独立
内部properties标签
<?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>
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///sms?serverTimezone=Asia/Shanghai&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="***"/>
</properties>
<environments default="development">
<!-- 可配置多个环境 并指定默认使用的环境-->
<environment id="development">
<!-- 指定事务管理器-->
<transactionManager type="JDBC"/>
<!-- 指定数据源 就是数据来自哪里 这里默认使用MyBatis自带的连接池-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<!-- //表示本机 localhost &就是& xml中&需要转译-->
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!-- 指定要加载的映射文件-->
<mappers>
<mapper resource="mapper/ProductMapper.xml"/>
</mappers>
</configuration>
为了更清晰的表达,标出变化之处:
外部配置文件
对于我们之前mybatis的mybatis-config.xml的文件,我们直接把jdbc相关参数配置到xml文件中去了,但是实际开发过程中数据量大,就会比较麻烦,我们可以建立一个单独的文件,进行存储,使用属性标签从文件中取值,此时就用到了外部配置文件
jdbc.properties位于resource下
在resource下建立一个jdbc.properties
driver = com.mysql.cj.jdbc.Driver
url = jdbc:mysql:///sms?serverTimezone=Asia/Shanghai&characterEncoding=utf8
username = root
password = ***
mybatis-config.xml就可以改为:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--外部标签关联属性文件-->
<properties resource="jdbc.properties" />
<environments default="development">
<!-- 可配置多个环境 并指定默认使用的环境-->
<environment id="development">
<!-- 指定事务管理器-->
<transactionManager type="JDBC"/>
<!-- 指定数据源 就是数据来自哪里 这里默认使用MyBatis自带的连接池-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<!-- //表示本机 localhost &就是& xml中&需要转译-->
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!-- 指定要加载的映射文件-->
<mappers>
<mapper resource="mapper/ProductMapper.xml"/>
</mappers>
</configuration>
当内部和外部属性出现同名时,则优先使用外部的;
别名(typeAliases)
typeAliases用于为Java的类型取别名,从而简化mapper中类名的书写
比如在mapper.xml中的resultType,要写包名加类名,书写不够简便。
注意typeAliases标签位于properties标签之下,书写顺序不能颠倒
为某个类定义别名
<!--为某个类定义别名-->
<typeAliases>
<!--type时全类名,alias取得时别名,可以省略,省略后默认的是类名,首字母不区分大小写-->
<typeAlias type="com.cx.pojo.Product" alias="prodcct" />
</typeAliases>
那么问题又来了,显示开发中,实体类会很多,这样会比较麻烦,我们可以直接扫描包,批量定义别名:
<!-- 批量定义别名-->
<typeAliases>
<!--扫描包,都设置为类名,首字母不区分大小写-->
<package name="com.cx.pojo"/>
</typeAliases>
上面两种定义别名的方式,用法一样,如下:
<?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.cx.mapper.ProductMapper">
<!--根据id进行查询-->
<select id="selectById" parameterType="int" resultType="Product">
select * from product where pid = #{pid}
</select>
<!--根据name进行模糊查询-->
<select id="selectByName" parameterType="String" resultType="product">
select * from product where pname like '%${新疆}%'
</select>
</mapper>
可以发现,resultType输出映射里面product实体类首字母大小写都可以。
别名冲突
使用package批量设置时很容易出现别名冲突,这是就需要使用@Alias
注解来为冲突的类单独设置别名
@Alias("products1")
public class Product {
private int pid;
private String pname;
private float price;
private Date pdate;
private String cid;
.....}
注意要扫描的是这个类的包,否则注解是无效的
下面mybatis已经存在的别名:
_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 |
映射(Mappers)
在mapper.xml文件中定义sql语句后,就必须让MyBatis知道,到哪里去找这些定义好的sql,这就需要在配置文件中指出要加载的mapper的位置;MyBatis支持4种方式来加载mapper文件
以下有四种方式:
- 直接指定mapper文件
resource
指定资源文件的相对目录 相对于classpath maven项目会自动将java和resources都添加到classpath中
所以相对与resources来指定路径即可
<mappers>
<mapper resource="mapper/ProductMapper.xml"/>
</mappers>
- 指定文件的绝对路径,MyBatis支持但是一般不用
url
<mappers>
<mapper url="file:///Users/jerry/Downloads/MYB/src/main/resources/mapper/ProductsMapper.xml"/>
</mappers>
- 通过指定接口 让MyBatis自动去查找对应的Mapper文件
class
这种方式要求映射文件和接口处于同一目录下,并且名称相同
要保证上述要求只需要在resources下创建于接口包名相同的目录即可
<mappers>
<mapper class="com.cx.mapper.ProductMapper"/>
</mappers>
注意:运行前先clean,否则可能因为之前已经存在target而不会重新编译,导致无法加载新建的mapper文件
4.指定包名称,扫描包下所有接口和映射文件
这种方式同样要求映射文件和接口处于同一目录下,并且名称相同
<mappers>
<package name="com.cx.mapper"/>
</mappers>
注意:
上面方法3和方法4,
对于普通项目:
我们可以把mapper.xml 和 接口 放在一个目录下
但是对于maven项目:
我们可以把接口文件放在java下,把对应的mapper.xml放在recources下;
1.如果是单级目录,只需保证包名一致:
即在Java和recources下创建同名的文件夹mapper;
2.如果是多级目录此时注意如果要保持目录一致:
即在java我们可以创建时输入 一级目录.二级目录.三级目录 等;
但是在recources下不可以这样创建,应该使用“/” 即:一级目录/二级目录/三级目录
此处是因为在recources下会把“.”当成文件名的一部分,创建一个包;
而Java会当成分别包的符号,创建多级目录,虽然看着一样,但是结构不同。
动态SQL
动态SQL指的是SQL语句不是固定死的,可以根据某些条件而发生相应的变化,这样的需求非常多见
例如:页面提供的搜索功能,要根据用户给出的一个或多个条件进行查询,查询条件不固定。
在JDBC中,我们通过Java代码来判断某个条件是否有效然后拼接SQL语句,这是非常繁琐的,MyBatis的动态SQL很好的解决了这个问题,使判断逻辑变得简洁直观;
在Mapper中通过标签来完成动态SQL的生成
1. if
从页面接受参数后我们会将参数打包为对象,然后将对象作为参数传给MyBatis执行查询操作,sql语句需要根据是否存在参数而动态的生成
接口声明方法:
//按条件查询
List<Product> selectByProduct(Product product);
mapper中:
<?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.cx.mapper.ProductMapper">
<!--if标签,按条件查询-->
<select id="selectByProduct" parameterType="product" resultType="product">
select * from product where 1=1
<if test="pid != null and pid != 0">
and pid = #{pid}
</if>
<if test="pname != '' and pname != null">
and pname like "%${pname}%"
</if>
</select>
</mapper>
where 1=1用于保持sql的正确性,当不存在条件时,则查询全部
测试:
private SqlSessionFactory sqlSessionFactory;
@Before
public void before() throws IOException {
//加载核心配置文件mybatis-config.xml
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//创建sqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//读取配置文件的流,创建sqlSessionFactory会话工厂
sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
}
@Test
public void selectByProduct(){
SqlSession sqlSession = sqlSessionFactory.openSession();
ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
Product product = new Product();
//按id查询
// product.setPid(2);
//根据名字模糊查询
product.setPname("葡萄");
List<Product> products = mapper.selectByProduct(product);
System.out.println(products);
sqlSession.close();
}
2.where
where的作用就是用于取出上面的where 1=1,因为这会让人看起来产生疑惑,其作用是将内部语句中的第一个and去除
<?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.cx.mapper.ProductMapper">
<!--if标签,按条件查询,加入where标签-->
<select id="selectByProduct" parameterType="product" resultType="product">
select * from product
<where>
<if test="pid != null and pid != 0">
and pid = #{pid}
</if>
<if test="pname != '' and pname != null">
and pname like "%${pname}%"
</if>
</where>
</select>
</mapper>
3. foreach
当一个条件中需要多个参数时则需要将多个参数拼接到一起,例如: in, not in
接口声明方法:
//方式一:参数是list类型
List<Product> selectByIds(List<Integer> ids);
//方式二:参数是product类型
List<Product> selectByidss(Product product);
mapper:
<!--foreach标签,parameterType 是list 按ids进行查询-->
<select id="selectByIds" parameterType="list" resultType="product">
select * from product
<where>
<if test="list != null">
<foreach collection="list" open="and pid in(" close=")" separator="," item="id">
#{id}
</foreach>
</if>
</where>
</select>
<!--方式二 参数类型是product类型-->
<select id="selectByidss" parameterType="product" resultType="product">
select * from product
<where>
<if test="ids != null">
<foreach collection="ids" open="and pid in (" close=")" separator="," item="id" >
#{id}
</foreach>
</if>
</where>
</select>
字符串:
<if test="typeList!=null and typeList.size > 0">
and
<foreach collection="typeList" item="type" index="index" open="(" close=")" separator="OR">
D.CODE like concat(concat('%', #{type}), '%')
</foreach>
</if>
<if test="ids != null"> 这里不仅判断属性是否为空还判断集合中是否有元素
foreache 标签属性说明:
强调:动态sql本质就是在拼接字符串,带着自己拼接sql的思路来编写动态sql会更好理解
collection 要遍历的集合
open 拼接的前缀
close 拼接的后缀
separator 拼接元素之间的分隔符
item 遍历得到的临时变量名
index 当前元素的索引(不常用)
注意方式二,参数类型是product类型,需要为POJO(Products)对象增加ids属性,并提供get和set方法
private List<Integer> ids;
public List<Integer> getIds() {
return ids;
}
public void setIds(List<Integer> ids) {
this.ids = ids;
}
书写测试类
//方式一测试方法
@Test
public void selectByIds(){
SqlSession sqlSession = sqlSessionFactory.openSession();
ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
List<Integer> ids = new ArrayList<>();
ids.add(2);
ids.add(3);
ids.add(4);
System.out.println(ids);
List<Product> products = mapper.selectByIds(ids);
System.out.println(products);
sqlSession.close();
}
//方式二测试方法
@Test
public void selectByidss(){
SqlSession sqlSession = sqlSessionFactory.openSession();
ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
List<Integer> ids = new ArrayList<>();
ids.add(2);
ids.add(3);
ids.add(4);
Product product = new Product();
product.setIds(ids);
List<Product> products = mapper.selectByidss(product);
System.out.println(products);
sqlSession.close();
}
4. set
set标签用于更新语句,当同时要更新多个字段时,我们需要留意当前是否是最后一个set,避免在后面出现,符号,使用set标签后可自动去除最后的逗号
mapper:
<update id="updateProductTest" parameterType="products">
update products
<set>
<if test="pname != null and pname != ''">
pname = #{pname},
</if>
<if test="price != null and price > 0">
price = #{price},
</if>
<if test="pdate != null">
pdate = #{pdate},
</if>
<if test="cid != null and cid != ''">
cid = #{cid},
</if>
</set>
where pid = #{pid}
</update>
测试代码:
private SqlSessionFactory sqlSessionFactory;
@Before
public void before() throws IOException {
//加载核心配置文件mybatis-config.xml
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//创建sqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//读取配置文件的流,创建sqlSessionFactory会话工厂
sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
}
//set测试
@Test
public void updateById(){
SqlSession sqlSession = sqlSessionFactory.openSession(true);
ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
Product product = mapper.selectById(2);
//Product product = new Product();
product.setPname("车厘子");
product.setPrice(1000.0);
mapper.updateById(product);
sqlSession.close();
}
5. sql与include
Sql中可将重复的sql提取出来,使用时用include引用即可,最终达到sql重用的目的。 例如可以吧sql语句中的字段列表提取出来作为通用的sql片段。然后在sql语句中使用 include 节点引用这个sql片段。
<!--提取片段-->
<sql id="fields">pid,pname,price,cid</sql>
<select id="includeTest" resultType="products">
select
<include refid="fields"/> <!-- 引用片段-->
from products
</select>
高级映射
在一些情况下数据库的记录和POJO对象无法直接映射,包括两种情形:
-
数据库字段与POJO字段名称不同(可以避免);
-
关联查询时,需要将关联表的数据映射为另一个类型的POJO(一对一),或List中(一对多);
在MyBatis中通过resultMap
来完成自定义映射
1.自定义字段与属性映射
当数据库列名
与自定义的映射对象的字段名
不一样时,就可以使用resultMap来自定义映射关系。
举个例子,orders表中数据库表为:
实体类为:
此时如果按上面那样直接返回类型写成Order类,因为字段不匹配,所以name会映射不上,结果就会输出“null”,这时候就可以按下面使用resultMap:
接口:
Order selectById(int id);
mpper:
<?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.cx.mapper.OrderMapper">
<resultMap id="order_map" type="order" autoMapping="true">
<id column="id" property="id" />
<result column="numbers" property="number" />
</resultMap>
<select id="selectById" parameterType="int" resultMap="order_map">
select * from orders where id = #{id}
</select>
</mapper>
对于自定义字段与属性映射,需要知道的是
- select 标签里的 resultMap的值对应于resultMap标签的id值
- resultMap 标签的type对应返回值类型
- autoMapping=“true” 自动映射,如果列名与字段名一致,就会自动映射,不一致可以通过id标签与result标签进行映射 ,如果不开启就算是名字完全一致,也需要自己一个一个写,麻烦
- id标签:主键定义映射标签,注意要放在result上面,colum 数据库对应列名,property对应实体类字段名
- result 普通属性定义标签,如果有id标签,放在id标签后面,colum 数据库对应列名 property对应的对象字段名
2. 关联查询
2.1 关联关系
两个表之间记录的对应关系,分为一对一和一对多。
而多对多则是三张表之间的关系,若掌握了两张表之间的一对多关系的处理,则多对多也就不是问题了,因为本质上多对多就是两个一对多组成的
这两张表之间存在一对一和一对多
站在user表角度来看 一个用户可能对应多个订单即一对多
站在orders表角度来看 一个订单只能对应一个用户即一对一
一对一查询
先来个案例:
需求:根据订单Id查询订单信息以及用户信息
Order类里面包含一个User类,我们要做的就是把对应的列名映射到User类中。把用户信息当成一个属性
书写pojo类
order类
public class Order {
private int id,user_id;
private String number;
private Date createtime;
private User user;//关联的类
}
user类
public class User {
private int id;
private String name;
private Date birthday;
private String sex,address;
}
当然不要忘了get/set方法噢
书写接口
Order selectAllById(int id);
书写mapper文件
<?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.cx.mapper.OrderMapper">
<!--一对一映射-->
<resultMap id="order_map2" type="order" autoMapping="true">
<id property="id" column="oid"/>
<result property="number" column="numbers" />
<association property="user" javaType="user" autoMapping="true">
<id property="id" column="uid" />
<result property="name" column="username"/>
</association>
</resultMap>
<select id="selectAllById" parameterType="int" resultMap="order_map2">
select * ,orders.id oid,user.id uid from orders inner join user
on orders.user_id = user.id
where orders.id = #{id}
</select>
</mapper>
书写测试类:
@Test
public void test2(){
SqlSession sqlSession = sqlSessionFactory.openSession();
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
Order order = mapper.selectAllById(2);
System.out.println(order);
}
- association 一对一映射,映射到类中去
- property 对应字段名
- javaType映射类型
- autoMapping=“true” 自动映射开启,如果不开启就算是名字完全一致,也需要自己一个一个写,麻烦
- 上面用法和普通映射一样,为了防止两个表的id名重复,所以在查询时取了别名,查询时采用的是内连接。
当连接查询出现重复字段时如:两个表都有id字段,MyBatis简单的取第一个匹配的字段值,很多时候这是不正确的,我们可以给这个重复的字段取个别名来避免,像这样:
一对多映射:
需求:根据用户id查询用户信息与用户订单信息
书写pojo
order:
public class Order {
private int id,user_id;
private String number;
private Date createtime;
}
user:
public class User {
private int id;
private String name;
private Date birthday;
private String sex,address;
private List<Order> orders;//产品类
}
接口:
package com.cx.mapper;
import com.cx.pojo.User;
public interface UserMapper {
//根据用户id查询用户信息,以及订单信息,一对多
User selectAllByUserId(int id);
}
mapper
<?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.cx.mapper.UserMapper">
<resultMap id="user_map" type="user" autoMapping="true">
<id property="id" column="uid" />
<result property="name" column="username" />
<collection property="orders" javaType="list" ofType="order" autoMapping="true">
<id property="id" column="oid" />
<result property="number" column="numbers" />
</collection>
</resultMap>
<!--一对多映射-->
<select id="selectAllByUserId" parameterType="int" resultMap="user_map">
select *,user.id uid,orders.id oid
from user join orders
on user.id = orders.user_id
where user.id = #{id}
</select>
</mapper>
测试类
@Test
public void selectAllByUserId(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectAllByUserId(1);
System.out.println(user);
}
autoMapping=“true” 将自动映射字段名与属性名能对应的字段,我们只需要添加对应不上的即
注意:无论如何在collection标签中必须至少存在一个手动映射字段
否则,将不会合并重复的主记录(user) 按照官方的说法,建议将手动映射id字段,可提高整体性能
另外collection标签中
collection 映射一个集合
property 映射的集合名
ofType用于指定元素的类型
javaType指定容器类型
一对多查询时,看resultMap的type,以它来做一个分组
嵌套映射,select
当关联查询非常复杂时,可以用嵌套的select,其原理是在映射复杂数据时执行另一个select来完成
上面查询可以写成下面形式:
<resultMap id="user_map2" type="user" autoMapping="true">
<id property="id" column="id" />
<result property="name" column="username" />
<!--
property指定数据要放在哪个属性中
javaType容器类型与属性对应
ofType元素类型
column要传给子查询的参数
select子查询的id
-->
<collection property="orders" javaType="list" ofType="order" column="id" select="selectOrderByUserid"/>
</resultMap>
<!-- 外层查询-->
<select id="selectAllByUserId2" parameterType="int" resultMap="user_map2">
select * from user where id = #{id}
</select>
<resultMap id="myorder" type="order" autoMapping="true">
<result property="number" column="numbers" />
</resultMap>
<select id="selectOrderByUserid" parameterType="int" resultMap="myorder">
select * from orders where user_id = #{id}
</select>
上面是一对多映射,当然一对一映射也是可以的,开始的一对一映射可以修改为:
<resultMap id="order_map3" type="order">
<id property="id" column="id" />
<result property="number" column="numbers" />
<result column="user_id" property="user_id" />
<!-- 指定嵌套查询 column是传给内层查询的参数 -->
<association property="user" javaType="user" column="user_id" select="selectByUserId"></association>
</resultMap>
<!-- 外层查询-->
<select id="selectAllById2" parameterType="int" resultMap="order_map3">
select * from orders where id = #{id}
</select>
<resultMap id="myMap" type="user" autoMapping="true">
<id column="id" property="id" />
<result property="name" column="username" />
</resultMap>
<!-- 嵌套查询-->
<select id="selectByUserId" parameterType="int" resultMap="myMap">
select * from user where id = #{user_id}
</select>
使用select 与不用select的区别:
-
不用select时对应的sql语句为关联查询,一次性返回所有数据,然后对其进行去重,提取
-
使用select时,sql语句多为单表查询,使用外层查询的某个字段作为内层查询的参数,是两条单独的sql语句
下一篇:(3)-mybatis之注解