深入学习MyBatis框架(3)

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中操作的是goodsgtype类,两个类之间有属性的关联关系;

单向关联

先通过商品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标签;

©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页