1、输入和输出映射
- 需要注意的是,设计表之后,尽量不要修改列名,如果修改列名,则整个Java实体类 model 和 mapper 映射文件都要改。
- 还要注意,我们在后面使用生成代码的方式,一旦代码生成,不要随意修改代码目录结构,因为引用关系之前已经设置好了。
- 使用实体类中的
属性和方法
区分:
在
mybatis
的映射文件中 ,表达式中所编写的内容在查找的时候,是通过方法关联,而非属性关联,当然绝大部分情况属性和方法是一致的,这涉及到OGNL
知识点;
private String aloginname;
public void setAname(String aname) {
this.aloginname = aname;
}
在 mybatis 中表达式 #{aname} 是可以获取数据的
private String aloginname;
public void setAloginname(String aname) {
this.aloginname = aname;
}
使用 #{aloginname} 获取数据
- 在
mybatis
中提供两组输入和输出映射支持方式:
输入参数映射 parameterMap="" parameterType=""
输出参数映射 resultType="" resultMap=""
输入参数
- 输出参数映射说明:
在前面的示例中,我们发现
paramterType
可以无需指定,不管参数是对象(插入,更新)类型还是基本数据类型(int),以上没有编写parameterType
都是因为我们的参数是单个,在实际开发中通常参数有很多个,比如分页查询(pageIndex当前页数,pageSize每页的条数),如果有多个参数,mybatis
如何处理?
public List<Account> selectLimit(Integer startNo, Integer pageSize);
<select id="selectLimit" resultType="account">
select * from account limit #{startNo},#{pageSize}
</select>
Exception in thread "main" org.apache.ibatis.exceptions.PersistenceException:
### Error querying database.
Cause:
org.apache.ibatis.binding.BindingException:Parameter 'startNo' not found. Available parameters are [arg1, arg0, param1, param2]
### Cause:
org.apache.ibatis.binding.BindingException: Parameter 'startNo' not found. Available parameters are [arg1, arg0, param1, param2]
以上关于异常的详细信息,是说mybatis会将多个参数以map的方式进行处理,在mapper.xml文件中引用参数的时候必须使用:
Available parameters are [arg1, arg0, param1, param2]
,其他参数继续往后写;
- 正确的写法是:
1、使用#{param}占位
mapper映射文件:
<select id="selectLimit" resultType="account">
select * from account limit #{param1},#{param2}
</select>
对应的接口:
public List<Account> selectLimit(Integer startNo, Integer pageSize);
测试类:
public class MyTest1 {
public static void main(String[] args) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
BooksMapper mapper = sqlSession.getMapper(BooksMapper.class);
System.out.println(JSON.toJSONString(mapper.selectLimit(1, 2), true));
}
}
mybatis中可以使用
#{param1}
……进行占位,可以传递多个参数;
2、使用注解传递多个参数
XML文件中的SQL语句
<select id="selectLimit" resultType="account">
select * from account limit #{startNo},#{pageSize}
</select>
mapper对应的接口中的写法
public List<Account> selectLimit(@Param("startNo") Integer startNo, @Param("pageSize") Integer pageSize);
测试类:
import com.alibaba.fastjson.JSON;
import com.oracle.mapper.AccountMapper;
import com.oracle.model.Account;
import com.oracle.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
public class MyTest7 {
public static void main(String[] args) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
List<Account> accounts = mapper.selectLimit(1, 2);
System.out.println(JSON.toJSONString(accounts, true));
}
}
mybatis
允许我们使用@Param注解
对参数进行命名;以上场景适用于参数不是很多,而且不是一个对象类型,如果参数太多,建议可以使用map作为参数或者直接编写一个对象参数;
3、使用 map 传递参数
mapper对应的接口:
public List<Account> selectLimit2(Map<String, Integer> map);
XML文件中的SQL语句:
<select id="selectLimit2" resultType="account">
select * from account limit limit #{startNo},#{pageSize}
</select>
测试类:
public class MyTest7 {
public static void main(String[] args) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
Map<String, Integer> map = new HashMap<>();
map.put("startNo", 1);
map.put("pageSize",2);
List<Account> accounts = mapper.selectLimit2(map);
System.out.println(JSON.toJSONString(accounts, true));
}
}
mybatis参数对应表
https://www.cnblogs.com/zhuangfei/p/9492915.html
了解;
以上处理中,很明显发现此时的键作为SQL填充占位符的变量名,值作为要填充的实际值;
4、如果查询条件复杂,查询条件来源于多个model如何处理
- mybatis支持从封装类中再次读取引用对象的属性值:
复杂的Java实体类:
public class QuerySearch {
private Account account;
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
}
接口:
public Account selectByQuery(QuerySearch q);
XML文件:
<select id="selectByQuery" resultType="account" parameterType="QuerySearch">
select * from account where aid=#{account.aid}
</select>
测试类:
public class MyTest8 {
public static void main(String[] args) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
QuerySearch querySearch = new QuerySearch();
Account account = new Account();
account.setAid(2);
querySearch.setAccount(account);
System.out.println(JSON.toJSONString(mapper.selectByQuery(querySearch), true));
}
}
mybatis通过
对象图导航语言 OGNL 表达式
可以获取传递对象参数中的属性值;
【总结】:
1、实际开发过程中,输入参数类型大部分都不需要指定,
2、参数可以使用基本数据类型,引用数据类型,一个参数,无需额外配置。
3、参数如果有多个,建议用@Param命名方式,超过3个用Map或者对象封装;
4、复杂查询sql的条件来源于多个实体类,可以使用包装查询条件类,做为参数,OGNL也可以解析。
输出参数
- 输出参数映射首先必须要指定,是基本数据类型,还是对象,或者是
map
;
1、map 返回值映射
接口:
public Map<String, String> query(Integer aid);
mapper映射文件:
<select id="query" resultType="map">
select * from account where aid=#{aid}
</select>
测试类:
import java.util.Map;
public class MyTest9 {
public static void main(String[] args) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
Map<String, Syting> query = mapper.query(2);
System.out.println(JSON.toJSONString(query, true));
}
}
mybatis将查询出来的结果集存储在map集合当中,将数据库中属性名作为键,值作为map中的值存进去;
2、mybatis还支持结果映射,可以指定某列作为map的key,值为对象
接口:
@MapKey("aname")
public Map<String, Account> query1(Integer aid);
mapper映射文件:
<select id="query1" resultType="map">
select * from account where aid=#{aid}
</select>
测试类:
import java.util.Map;
public class MyTest9 {
public static void main(String[] args) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
Map<String, Account> query = mapper.query1(2);
System.out.println(JSON.toJSONString(query, true));
}
}
3、如果查询的列和对象的属性不一致怎么办?
查询的列和对象的属性不一致的情况下,之前在SQL语句中使用as关键字进行处理,这里还可以使用如下方式:
mapper.xml文件:
<!--自定义结果类型转换-->
<!--orm的m mapping-->
<!--mapping 规则是 类对应表, 属性对应列-->
<resultMap id="resultMapAccount" type="account" autoMapping="true">
<!--主键比较特殊,需要单独使用id进行配置-->
<id property="aid" column="aid" />
<!--属性和表的列 对应关系-->
<result column="a_nikename" property="anikename"/>
</resultMap>
<select id="selectAll" resultMap="resultMapAccount" >
select * from account
</select>
测试类:
public class MyTest9 {
public static void main(String[] args) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
List<Account> accounts = mapper.selectAll();
System.out.println(JSON.toJSONString(accounts, true));
}
}
resultMap
可以做resultType
能做的事情,还可以做的更多。- 由于数据库中的字段名为:a_nikename,但是实体类里面属性为:anikename,所以查询出来的结果无法映射成功,但此时经过这样的配置之后,自定义结果转换类型,并给定一个ID,将查询出来返回值设定为该ID类型,并将该自定义的转换类型设置为自动适配,这样就可以映射成功!
resultMap
是指定自定义类型,resultType
是指定本身就存在的类型;
- 除了配置
resultMap
,还可以在mybatis-config.xml文件中
配置全局去除下划线:
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
2、多表关联查询
- 数据库中多对多查询中,表和表之间存在主外键关系,它指的是一张表中的列参考来源于另一张表中某个列的值。两张表之间可以通过列建立外键关系,从而关联数据;
- 以下的案例我们都采用学生表和电脑表的关系;数据库表之间建立关联关系之后,在数据层面会有三种体现:
- Java对象之间的关系:
如果是一对一关系中,学生对象中维护电脑对象,电脑对象中维护学生对象;
如果是一对多关系,学生对象维护电脑对象的集合,电脑对象中维护学生对象;
如果是多对多关系,学生对象维护电脑对象的集合,电脑对象中维护学生对象的集合;
有人会说我们可以像数据库表之间的关系一样,如果是一对一的关系中,让学生实体类中存储电脑实体类的电脑ID属性,电脑实体类中存储学生的ID,为什么维护一个对象?这是因为如果只封装另一个关联实体类的ID,是完全没有意义的;并且将来做多表查询的时候,我们返回的结果集大多数都是两张表中所有数据,这时候只维护一个ID的实体类完全没有意义;
多表关联查询一对多
单向关联
- 在这里我们设计了两张表:商品表与商品种类表,一个商品对应一个种类,一个种类对应多个商品;
1、编写数据库脚本:
-- 商品信息
CREATE TABLE `goods` (
`gid` int(11) NOT NULL auto_increment COMMENT '商品编号',
`gname` varchar(50) NOT NULL COMMENT '商品名称',
`gcount` int NOT NULL COMMENT '商品库存数量',
`gprice` FLOAT NULL COMMENT '商品售价',
`gdes` varchar(100) NULL COMMENT '商品描述',
`typeid` int not null COMMENT '商品归属的类别', PRIMARY KEY (`gid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 商品类别表
CREATE TABLE `gtype` (
`tid` int NOT NULL PRIMARY key auto_increment COMMENT '类别编号',
`tname` varchar(50) NOT NULL COMMENT '类别名称'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 增加主外键关系
ALTER TABLE `goods` ADD CONSTRAINT `fk_typeid` FOREIGN KEY (`typeid`) REFERENCES `gtype` (`tid`);
2、编写Java实体类:
public class Goods {
private Integer gid;
private String gname;
private Integer gcount;
private Double gprice;
private String gdes;
……
}
public class GType {
private Integer tid;
private String tname;
private List<Goods> goods;
……
}
【注意】:商品作为一对多关系中的一的一方,维护了多的一方的对象;因为这里是单向关联查询,也就是只能通过商品查询到种类,但是不能通过种类查询到商品,所以这里先不给多的一方对象中设置一的一方的集合;
3、编写mapper.xml文件与其对应的接口进行CRUD操作:
单表查询,通过类别的ID查询类别详细信息
public GType selectGTpeById(Integer gid);
<select id="selectGTpeById" resultType="gtype">
select * from gtype where tid=#{tid}
</select>
@Test
public void selectGoodsById(){
GTypeMapper mapper = sqlSession.getMapper(GTypeMapper.class);
System.out.println(JSON.toJSONString(mapper.selectGTpeById(1), true));
}
这是一个简单的通过ID查询类别;
通过ID查询商品类别,并关联类别下的商品集合信息
@Test
public void query1(){
GTypeMapper mapper = sqlSession.getMapper(GTypeMapper.class);
System.out.println(JSON.toJSONString(mapper.query1(1), true));
}
public GType query1(Integer tid);
<select id="query1" resultType="GType">
select * from gtype t left join goods g on t.tid=g.typeid where t.tid=#{tid}
</select>
这时候select的语句的返回值是一个GType类型,多表查询的结果一定是多条的,因为一个种类对应多个商品,可以发现多表查询的结果是无法匹配Java实体类的,数据库查出来的结果如下所示:
但是mybatis
只解析了前面两个字段,要想匹配的上,必须使用resultMap
进行映射;
使用resultMap解析多表查询的结果
<resultMap id="gtypeMap1" type="gtype">
<id column="tid" property="tid"/>
<result column="tname" property="tname"/>
<!--多表查询中一的一方中维护多的一方中的集合,这里进行映射的时候使用collection标签-->
<collection property="goods" ofType="goods">
<id column="gid" property="gid"/>
<result column="gname" property="gname"/>
<result column="gcount" property="gcount"/>
<result column="gprice" property="gprice"/>
<result column="gdes" property="gdes"/>
</collection>
</resultMap>
<select id="query2" resultMap="gtypeMap1">
select * from gtype t left join goods g on t.tid=g.typeid where t.tid=#{tid}
</select>
public GType query2(Integer tid);
@Test
public void query2(){
GTypeMapper mapper = sqlSession.getMapper(GTypeMapper.class);
System.out.println(JSON.toJSONString(mapper.query2(1), true));
}
查询出来的结果为:
{
"goods":[
{
"gcount":12045,
"gdes":"小巧好用",
"gid":1,
"gname":"平板电脑",
"gprice":3850.0
},
{
"gcount":3200,
"gdes":"处理器骁龙865",
"gid":2,
"gname":"手机",
"gprice":2400.0
},
{
"gcount":1450,
"gdes":"超薄、高清",
"gid":3,
"gname":"电视",
"gprice":4100.0
}
],
"tid":1,
"tname":"电子产品"
}
查询所有的商品类别,以及关联的商品数据
将所有商品类别信息查询出来,并查询他关联的商品数据;
public List<GType> queryAll();
@Test
public void queryAll(){
GTypeMapper mapper = sqlSession.getMapper(GTypeMapper.class);
System.out.println(JSON.toJSONString(mapper.queryAll(), true));
}
<resultMap id="gtypeMap1" type="gtype">
<id column="tid" property="tid"/>
<result column="tname" property="tname"/>
<!--多表查询中一的一方中维护多的一方中的集合,这里进行映射的时候使用collection标签-->
<collection property="goods" ofType="goods">
<id column="gid" property="gid"/>
<result column="gname" property="gname"/>
<result column="gcount" property="gcount"/>
<result column="gprice" property="gprice"/>
<result column="gdes" property="gdes"/>
</collection>
</resultMap>
<select id="queryAll" resultMap="gtypeMap1">
select * from gtype t left join goods g on t.tid=g.typeid
</select>
查询结果:
[
{
"goods":[
{
"gcount":12045,
"gdes":"小巧好用",
"gid":1,
"gname":"平板电脑",
"gprice":3850.0
},
{
"gcount":3200,
"gdes":"处理器骁龙865",
"gid":2,
"gname":"手机",
"gprice":2400.0
},
{
"gcount":1450,
"gdes":"超薄、高清",
"gid":3,
"gname":"电视",
"gprice":4100.0
}
],
"tid":1,
"tname":"电子产品"
},
{
"goods":[
{
"gcount":10,
"gdes":"奶油",
"gid":4,
"gname":"巧乐兹",
"gprice":8.0
},
{
"gcount":12,
"gdes":"呀土豆",
"gid":5,
"gname":"薯片",
"gprice":6.0
}
],
"tid":2,
"tname":"零食"
},
{
"goods":[
{
"gcount":1233,
"gdes":"水杯很好用",
"gid":6,
"gname":"水杯",
"gprice":23.0
}
],
"tid":3,
"tname":"水杯"
}
]
使用automapping
简化查询所有的映射
上面我们自定义了一个
resultMap
,但是发现这样做很繁琐,需要将结果集的每一个列都进行映射,我们可以使用automapping
属性让结果集中的属性自动映射到指定的type类型中;
<resultMap id="gtypeMap2" type="gtype" autoMapping="true">
<!--多表查询中一的一方中维护多的一方中的集合,这里进行映射的时候使用collection标签-->
<collection property="goods" ofType="goods" autoMapping="true"/>
</resultMap>
<select id="queryAll1" resultMap="gtypeMap2">
select * from gtype t left join goods g on t.tid=g.typeid
</select>
public List<GType> queryAll1();
@Test
public void queryAll1(){
GTypeMapper mapper = sqlSession.getMapper(GTypeMapper.class);
System.out.println(JSON.toJSONString(mapper.queryAll1(), true));
}
最终控制台输出和上面一样的结果;
延迟加载
上面的查询结果中可以发现,每次再查看商品类别的时候,对应的商品信息也查询出来了,但有时候我们可能并不需要查看这些商品信息,可以使用
mybatis
中的延迟加载,延迟加载意思是将查询类别信息和商品信息分为两个SQL语句执行,当我们访问商品信息的时候,再去查询对应的这部分信息,如果不访问,则不查询;这样一来就提高程序运行的效率,也提高了灵活性;
public List<GType> queryLazy(Integer tid);
@Test
public void queryLazy(){
GTypeMapper mapper = sqlSession.getMapper(GTypeMapper.class);
System.out.println(JSON.toJSONString(mapper.queryLazy(1), true));
}
<resultMap id="gtypeMap3" type="gtype" autoMapping="true">
<!--多表查询中一的一方中维护多的一方中的集合,这里进行映射的时候使用collection标签-->
<collection
property="goods"
column="tid"
select="com.oracle.mapper.GoodsMapper.selectGoodsById"
fetchType="lazy"/>
</resultMap>
<select id="queryLazy" resultMap="gtypeMap3">
select * from gtype where tid=#{tid}
</select>
<mapper namespace="com.oracle.mapper.GoodsMapper">
<select id="selectGoodsById" resultType="goods">
select * from goods where typeid=#{tid}
</select>
</mapper>
运行结果:
[
{
"goods":[
{
"gcount":12045,
"gdes":"小巧好用",
"gid":1,
"gname":"平板电脑",
"gprice":3850.0
},
{
"gcount":3200,
"gdes":"处理器骁龙865",
"gid":2,
"gname":"手机",
"gprice":2400.0
},
{
"gcount":1450,
"gdes":"超薄、高清",
"gid":3,
"gname":"电视",
"gprice":4100.0
}
],
"tname":"电子产品"
}
]
这里可以看到延迟加载已经起了作用,SQL语句分为了两条执行,不再是之前的一条SQL语句;
- 这里mapper.xml配置文件中第一个SQL执行的结果集封装在了自定义的
resulyMap
中,在这里面将查询出来的List集合使用抓取策略进行懒加载,使用的是collection标签,其中的select属性的值指的是第二个SQL语句的ID,如果这个SQL语句在其他的mapper.xml
文件中,需要写上它的namespace
;fetchType
是抓取策略,lazy
是延迟加载的意思 ,eager
积极的是默认的;- collection标签中省略了
ofType
以及autoMapping
属性,是因为第二个SQL里面已经配置过了;
双向关联
上面是单向关联,指的是只能通过商品类别找出商品信息,但是不能通过商品信息找到商品类别信息,而这里的双向就是解决了这个问题;
- 在一对多关联中,使得多的一方维护一的一方的一个对象,Java实体类如下:
public class Goods {
private Integer gid;
private String gname;
private Integer gcount;
private Double gprice;
private String gdes;
private GType gtype;
……
}
public class GType {
private Integer tid;
private String tname;
private List<Goods> goods;
……
}
通过ID只查询商品信息
public Goods selectById(Integer gid);
@Test
public void selectById(){
GoodsMapper mapper = sqlSession.getMapper(GoodsMapper.class);
System.out.println(JSON.toJSONString(mapper.selectById(1), true));
}
<select id="selectById" resultType="goods">
select * from goods where gid=#{gid}
</select>
测试结果:
{
"gcount":12045,
"gdes":"小巧好用",
"gid":1,
"gname":"平板电脑",
"gprice":3850.0
}
通过ID查询商品信息并关联类别信息1
public Goods query1(Integer gid);
@Test
public void query1(){
GoodsMapper mapper = sqlSession.getMapper(GoodsMapper.class);
System.out.println(JSON.toJSONString(mapper.query1(1), true));
}
<resultMap id="goodsMap1" type="goods" autoMapping="true">
<result column="tid" property="gtype.tid"/>
<result column="tname" property="gtype.tname"/>
</resultMap>
<select id="query1" resultMap="goodsMap1">
select
*
from
goods g
inner join
gtype t
on
g.typeid=t.tid
where
gid=#{gid}
</select>
查询结果:
{
"gcount":12045,
"gdes":"小巧好用",
"gid":1,
"gname":"平板电脑",
"gprice":3850.0,
"gtype":{
"tid":1,
"tname":"电子产品"
}
}
以上就是通过商品ID查询详细信息,但是会发现由于gtype无法与查询的列自动对应,需要手动进行关联,显得很麻烦,可以通过以下方法简化书写;
通过ID查询商品信息并关联类别信息2
<resultMap id="goodsMap2" type="goods" autoMapping="true">
<association property="gtype" autoMapping="true" javaType="gtype"/>
</resultMap>
<select id="query2" resultMap="goodsMap2">
select
*
from
goods g
inner join
gtype t
on
g.typeid=t.tid
where
gid=#{gid}
</select>
public Goods query2(Integer gid);
@Test
public void query2(){
GoodsMapper mapper = sqlSession.getMapper(GoodsMapper.class);
System.out.println(JSON.toJSONString(mapper.query2(1), true));
}
association
标签是用来映射单个对象,比result
简单了很多,查询出来的结果和上面一样;一对多查询中,多的一方关联一的一方,推荐使用association
标签;
通过ID查询商品信息并关联类别信息以及延迟加载
public List<GType> queryLazy(Integer tid);
@Test
public void queryLazy(){
GTypeMapper mapper = sqlSession.getMapper(GTypeMapper.class);
System.out.println(JSON.toJSONString(mapper.queryLazy(1), true));
}
<resultMap id="goodsMap3" type="goods" autoMapping="true">
<association
property="gtype"
column="typeid"
select="com.oracle.mapper.GTypeMapper.selectGTpeById"
fetchType="lazy"
/>
</resultMap>
<select id="queryLazy" resultMap="goodsMap3">
select
*
from
goods
where
gid=#{gid}
</select>
<select id="selectGTpeById" resultType="gtype">
select * from gtype where tid=#{typeid}
</select>
查询结果:
{
"gcount":12045,
"gdes":"小巧好用",
"gid":1,
"gname":"平板电脑",
"gprice":3850.0,
"gtype":{
"tid":1,
"tname":"电子产品"
}
}
很明显可以看到,执行了两条SQL语句;
查询所有信息
public List<Goods> queryAll();
@Test
public void queryAll(){
GoodsMapper mapper = sqlSession.getMapper(GoodsMapper.class);
System.out.println(JSON.toJSONString(mapper.queryAll(), true));
}
<resultMap id="goodsMap3" type="goods" autoMapping="true">
<association
property="gtype"
column="typeid"
select="com.oracle.mapper.GTypeMapper.selectGTpeById"
fetchType="lazy"
/>
<select id="queryAll" resultMap="goodsMap3">
select
*
from
goods
</select>
<select id="selectGTpeById" resultType="gtype">
select * from gtype where tid=#{typeid}
</select>
查询结果:
[
{
"gcount":12045,
"gdes":"小巧好用",
"gid":1,
"gname":"平板电脑",
"gprice":3850.0,
"gtype":{
"tid":1,
"tname":"电子产品"
}
},
{
"gcount":3200,
"gdes":"处理器骁龙865",
"gid":2,
"gname":"手机",
"gprice":2400.0,
"gtype":{"$ref":"$[0].gtype"}
},
……
]
- 本质上最后的结果是正确的,但为什么最后输出的结果中出现了
{"$ref":"$[0].gtype"}
奇怪的结果,其实这是由于我们使用的FastJSON的循环引用造成的,循环引用:当一个对象包含另一个对象时,fastjson
就会把该对象解析成引用。引用是通过$ref
标示的,下面介绍一些引用的描述:
"$ref":".." 上一级
"$ref":"@" 当前对象,也就是自引用
"$ref":"$" 根对象
"$ref":"$.children.0" 基于路径的引用,相当于 root.getChildren().get(0)
- 仔细观察可以发现只有第一个输出该类别的商品的结果是正确的,再以后的都是错误的,也就是第一次查询到该类别的商品之后,gtype中维护的list集合不为空了,因此出现了循环引用的现象;使用循环遍历输出就不会有这样的结果出现了;
多表关联查询多对多
- 准备SQL脚本:
-- 商品信息
CREATE TABLE `goods` (
`gid` int(11) NOT NULL auto_increment COMMENT '商品编号',
`gname` varchar(50) NOT NULL COMMENT '商品名称',
`gcount` int NOT NULL COMMENT '商品库存数量',
`gprice` FLOAT NULL COMMENT '商品售价',
`gdes` varchar(100) NULL COMMENT '商品描述',
PRIMARY KEY (`gid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 商品类别表
CREATE TABLE `gtype` (
`tid` int NOT NULL PRIMARY key auto_increment COMMENT '类别编号',
`tname` varchar(50) NOT NULL COMMENT '类别名称'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 多对多使用关联关系表维护关系
create table gtype_goods(
tgid int not null PRIMARY key auto_increment COMMENT '主键值',
goodsid int not null COMMENT '商品编号',
gtypeid int not null COMMENT '商品类型编号'
)
-- 添加主外键关系
ALTER TABLE `gtype_goods` ADD CONSTRAINT `fk_gtypeid_gtype` FOREIGN KEY (`gtypeid`) REFERENCES `gtype` (`tid`);
ALTER TABLE `gtype_goods` ADD CONSTRAINT `fk_goodsid_goods` FOREIGN KEY (`goodsid`) REFERENCES `goods` (`gid`);
- Java实体类引用关系:
因为现在研究的是多对多的关系,因此商品实体类与商品种类实体类中都维护了对方的
List集合
;
但要注意:作为中间表的关联关系表不需要定义Java实体类;因为在java中操作的是goods
和gtype
类,两个类之间有属性的关联关系;
单向关联
先通过商品goods关联gtype;
- 编写Java实体类:
public class Goods {
private Integer gid;
private String gname;
private Integer gcount;
private Double gprice;
private String gdes;
……
}
public class GType {
private Integer tid;
private String tname;
……
}
1、根据商品ID查询商品简单信息
<select id="selectById" resultType="goods">
select * from goods where gid=#{gid}
</select>
--- 接口中的方法:
public Goods selectById(Integer gid);
--- 测试类:
@Test
public void selectById(){
GoodsMapper mapper = sqlSession.getMapper(GoodsMapper.class);
Goods goods = mapper.selectById(1);
System.out.println(JSON.toJSONString(goods, true));
}
2、根据商品ID查询出商品信息以及对应的类别信息
<resultMap id="goodsMap1" type="goods" autoMapping="true">
<id property="gid" column="gid"></id>
<collection property="types" autoMapping="true" ofType="gtype"/>
</resultMap>
<select id="queryAllInfoById" resultMap="goodsMap1">
SELECT
gtype.tname,
gtype.tid,
goods.gid,
goods.gname,
goods.gcount,
goods.gprice,
goods.gdes
FROM
goods
LEFT JOIN gtype_goods ON gtype_goods.goodsid = goods.gid
LEFT JOIN gtype ON gtype.tid = gtype_goods.gtypeid
WHERE
goods.gid = #{gid}
</select>
--- 接口中的方法:
//通过商品的ID查询出商品的详细信息
public Goods queryAllInfoById(Integer gid);
--- 测试类:
@Test
public void queryAllInfoById(){
GoodsMapper mapper = sqlSession.getMapper(GoodsMapper.class);
Goods goods = mapper.queryAllInfoById(1);
System.out.println(JSON.toJSONString(goods, true));
}
这里的SQL采用的是关联查询,使用中间表进行关联;
由于查询出来的类型信息需要存放在实体类里面的List集合中,因此在自定义的resultMap
中商品的类别信息使用Collection标签
进行自动匹配;
3、根据商品ID查询出商品信息延迟加载查询出类别信息
<resultMap id="goodsMap2" type="goods" autoMapping="true">
<id column="gid" property="gid"></id>
<collection
property="types"
fetchType="lazy"
column="gid"
select="com.oracle.mapper.GTypeMapper.selectGTypesByGoodsId"
autoMapping="true"/>
</resultMap>
<select id="selectGoodsLazyType" resultMap="goodsMap2">
select * from goods where gid=#{gid}
</select>
<mapper namespace="com.oracle.mapper.GTypeMapper">
<select id="selectGTypesByGoodsId" resultType="gtype">
SELECT
gtype.tname,
gtype.tid
FROM
gtype
LEFT JOIN
gtype_goods
ON
gtype_goods.gtypeid = gtype.tid
WHERE
gtype_goods.goodsid=#{gid}
</select>
</mapper>
--- 接口中的方法:
//通过商品ID查询所有详细信息两条SQL实现懒加载
public Goods selectGoodsLazyType(Integer gid);
--- 测试类:
@Test
public void selectGoodsLazyType() {
GoodsMapper mapper = sqlSession.getMapper(GoodsMapper.class);
Goods goods = mapper.selectGoodsLazyType(1);
System.out.println(goods.getGname());
System.out.println("=============================================");
System.out.println(JSON.toJSONString(goods.getTypes(), true));
}
4、查询全部
<resultMap id="goodsMap2" type="goods" autoMapping="true">
<id column="gid" property="gid"></id>
<collection
property="types"
fetchType="lazy"
column="gid"
select="com.oracle.mapper.GTypeMapper.selectGTypesByGoodsId"
autoMapping="true"/>
</resultMap>
<select id="selectAll" resultMap="goodsMap2">
select * from goods
</select>
<mapper namespace="com.oracle.mapper.GTypeMapper">
<select id="selectGTypesByGoodsId" resultType="gtype">
SELECT
gtype.tname,
gtype.tid
FROM
gtype
LEFT JOIN
gtype_goods
ON
gtype_goods.gtypeid = gtype.tid
WHERE
gtype_goods.goodsid=#{gid}
</select>
</mapper>
//查询所有
public List<Goods> selectAll();
--- 测试类:
@Test
public void selectAll() {
GoodsMapper mapper = sqlSession.getMapper(GoodsMapper.class);
List<Goods> goods = mapper.selectAll();
System.out.println(JSON.toJSONString(goods, true));
}
测试结果:
[
{
"gcount":1011,
"gdes":"学生必备",
"gid":1,
"gname":"手机",
"gprice":4500.0,
"types":[
{
"tid":1,
"tname":"电子产品"
},
{
"tid":2,
"tname":"数码产品"
}
]
},
{
"gcount":1000,
"gdes":"新手必备",
"gid":2,
"gname":"华硕笔记本",
"gprice":5000.0,
"types":[
{
"tid":2,
"tname":"数码产品"
},
{
"tid":1,
"tname":"电子产品"
}
]
},
{
"gcount":120,
"gdes":"丝一般柔顺",
"gid":3,
"gname":"巧克力",
"gprice":25.0,
"types":[
{
"tid":3,
"tname":"休闲零食"
},
{
"tid":4,
"tname":"甜点"
}
]
},
{
"gcount":143,
"gdes":"很甜",
"gid":4,
"gname":"雪糕",
"gprice":10.0,
"types":[]
},
{
"gcount":1234,
"gdes":"耐用",
"gid":5,
"gname":"水杯",
"gprice":100.0,
"types":[
{
"tid":5,
"tname":"生活用品"
}
]
}
]
5、传递多个参数的查询
这里只是学习它的语法,不要太在意这个操作的意义;
<resultMap id="goodsMap3" type="goods" autoMapping="true">
<id column="gid" property="gid"/>
<collection property="types"
column="{goodsId=gid,goodsName=gname}"
fetchType="lazy"
select="com.oracle.mapper.GTypeMapper.queryGTypes"
/>
</resultMap>
<select id="queryByParams" resultMap="goodsMap3">
select * FROM goods
</select>
<select id="queryGTypes" resultType="gtype">
SELECT gtype.tid,gtype.tname FROM gtype_goods
INNER JOIN gtype ON gtype_goods.gtypeid=gtype.tid
WHERE gtype_goods.goodsid = #{goodsId} AND gtype.tname LIKE CONCAT('%',#{goodsName},'%')
</select>
//传递多个参数
public List<Goods> queryByParams();
--- 测试类:
@Test
public void selectAll() {
GoodsMapper mapper = sqlSession.getMapper(GoodsMapper.class);
List<Goods> goods = mapper.queryByParams();
System.out.println(JSON.toJSONString(goods, true));
}
双向关联
上面的例子都是单向,所谓单向指的是只能通过商品查找到类别的信息,类别不能直接找到商品,Java实体类GType中没有任何商品的字段;一下的例子都是双向的;
多条SQL延迟加载
<resultMap id="resultMap1" type="gtype" autoMapping="true">
<id column="tid" property="tid"></id>
<collection
property="goodsList"
column="tid"
select="com.oracle.mapper.GoodsMapper.queryGoodsByTypeid"/>
</resultMap>
<select id="queryById" resultMap="resultMap1">
select * from gtype where tid=#{tid}
</select>
<select id="queryGoodsByTypeid" resultType="goods">
select * from goods
inner join gtype_goods ON gtype_goods.goodsid = goods.gid
where gtype_goods.gtypeid = #{tid}
</select>
public GType queryById(Integer typeid);
@Test
public void queryById(){
GTypeMapper mapper = sqlSession.getMapper(GTypeMapper.class);
GType gType = mapper.queryById(2);
System.out.println(JSON.toJSONString(gType, true));
}
测试结果:
{
"goodsList":[
{
"gcount":1000,
"gdes":"新手必备",
"gid":2,
"gname":"华硕笔记本",
"gprice":5000.0
},
{
"gcount":1011,
"gdes":"学生必备",
"gid":1,
"gname":"手机",
"gprice":4500.0
}
],
"tid":2,
"tname":"数码产品"
}
增加商品图片表
在上面多对多的基础上增加一张商品图片表,使得一个商品可以对应多个图片,一个图片只能对应一个商品,产生一对多的关系;
编写数据库脚本:
-- 商品图片信息表,每一张图片属于一个商品
CREATE TABLE productImg(
piid INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '图片唯一标识',
ppath VARCHAR(50) NOT NULL COMMENT '图片上传的路径(名称)',
goodsid INT NOT NULL COMMENT '商品编号'
)ENGINE=INNODB DEFAULT CHARSET=utf8;
ALTER TABLE productImg ADD CONSTRAINT fk_goodsid_productImg_goods FOREIGN KEY(goodsid) REFERENCES goods(gid)
编写Java实体类:
public class ProductImg {
private Integer ppid;
private String ppath;
private Goods goods;
……
}
public class Goods {
private Integer gid;
private String gname;
private Integer gcount;
private Double gprice;
private String gdes;
//多对多关系中,这里先使用单向的关联
//所以Goods中维护了另一张表的List集合
private List<GType> types;
private List<ProductImg> productImgList;
……
}
使用商品ID查找对应的图片信息
//使用商品ID查找图片信息
public Goods selectImgsById(Integer gid);
@Test
public void selectImgsById() {
GoodsMapper mapper = sqlSession.getMapper(GoodsMapper.class);
Goods goods = mapper.selectImgsById(1);
System.out.println(JSON.toJSONString(goods, true));
}
<resultMap id="goodsMap4" type="goods" autoMapping="true">
<id column="gid" property="gid"></id>
<collection
property="productImgList"
column="gid"
fetchType="lazy"
select="com.oracle.mapper.ProductImgMapper.selectByGid"
/>
</resultMap>
<select id="selectImgsById" resultMap="goodsMap4">
select * from goods where gid=#{gid}
</select>
<mapper namespace="com.oracle.mapper.ProductImgMapper">
<select id="selectByGid" resultType="productimg">
select * from productimg where goodsid=#{gid}
</select>
</mapper>
查询结果:
{
"gcount":1011,
"gdes":"学生必备",
"gid":1,
"gname":"手机",
"gprice":4500.0,
"productImgList":[
{
"ppath":"D:\\桌面图标\\Java\\练习文件打包\\temp\\主页.png"
},
{
"ppath":"D:\\桌面图标\\C++\\练习文件打包\\temp.jpg"
}
]
}
通过图片ID查找对应的商品信息,再关联到类别信息
XML文件:
--- ProductImgMapper.xml文件中:
<resultMap id="resultMap1" type="productimg" autoMapping="true">
<id column="piid" property="piid"></id>
<association
property="goods"
column="goodsid"
select="com.oracle.mapper.GoodsMapper.selectById1"
fetchType="lazy"
/>
</resultMap>
<select id="queryImgById" resultMap="resultMap1">
select * from productimg where piid=#{piid}
</select>
--- GoodsMapper.xml文件中:
<resultMap id="goodsMap5" type="goods" autoMapping="true">
<id column="gid" property="gid"></id>
<collection
property="types"
select="com.oracle.mapper.GTypeMapper.selectGTypesByGoodsId"
column="gid"
fetchType="lazy"
/>
</resultMap>
<select id="selectById1" resultMap="goodsMap5">
select * from goods where gid=#{gid}
</select>
--- GTypeMapper.xml文件中:
<select id="selectGTypesByGoodsId" resultType="gtype">
SELECT
gtype.tname,
gtype.tid
FROM
gtype
LEFT JOIN
gtype_goods
ON
gtype_goods.gtypeid = gtype.tid
WHERE
gtype_goods.goodsid=#{gid}
</select>
接口与测试类:
public ProductImg queryImgById(Integer piid);
@Test
public void queryImgById(){
ProductImgMapper mapper = sqlSession.getMapper(ProductImgMapper.class);
ProductImg productImg = mapper.queryImgById(1);
System.out.println(JSON.toJSONString(productImg, true));
}
{
"goods":{
"gcount":1011,
"gdes":"学生必备",
"gid":1,
"gname":"手机",
"gprice":4500.0,
"types":[
{
"tid":1,
"tname":"电子"
},
{
"tid":2,
"tname":"数码产品"
}
]
},
"piid":1,
"ppath":"D:\\桌面图标\\Java\\练习文件打包\\temp\\主页.png"
}
实际开发中,我们一般不会这么大跨度的查询,这里只是作为练习;
注意多表查询中尽量使用懒加载,减少不必要的查询;
关联关系映射小结
- 一对多关系中,首先数据库表与表之间建立主外键关联关系,如果没有外键关联关系,通过建立表与表之间数据的逻辑引用关系也可以;
- 其次在Java实体类中,维护对象的关系,一种是对象关联,一种是集合关联;
- mapper映射文件中:
1、
resultType
标签解决不了的问题,要使用强大的resultMap
标签;
2、可以一条SQL直接关联查询,也可以多条SQL分开查询;
3、一条SQL查询不支持懒加载,多条SQL查询通过fetchType=lazy
实现懒加载;
4、多条SQL关联查询时,会出现传递参数问题,参数可以有一个,也可以有多个;
5、当关联对象是单个对象类型使用association
,当关联对象试剂盒类型使用collection
关联;
6、一条SQL查询的时候,association
需要指定javaType
;
7、一条SQL查询的时候,collection
需要指定ofType
;
8、对于查询列和Java属性一致的情况,使用autoMapping=true
简化配置;
9、不论单向关联、双向关联、一对多映射、多对多映射,建议resultMap
标签中都编写id标签;