发表于: 2018-09-17 23:23:38
3 417
因为对mybatis实现多对一\一对多\多对多,以及resultType\resultMap\collection\association的原理不熟,然后用一天的时间将自己过去学习的内容作了回顾和梳理:
1.mybatis主要是为了解决什么问题?
SQL语句太长,不好管理\编辑
2.怎么来的,原理是怎么回事?
网上有一大堆的原理图,随便找一份差不多就是这个意思,
可能是某天某个程序员用JDBC改数据库,结果太辛苦了,在权衡删库还是造轮子,选择了造轮子,这个轮子的目的是减少JDBC中重复的SQL语句和重复代码,
具体包括:连接表\CRUD\多表操作等.
3.都有哪些功能,分别解决什么问题,如何解决CRUD问题的?
首先我们有个数据库,数据库里面有个表,表有字段,有数据,然后你想知道这个表格里的信息,怎么办?
1.直接通过电脑端的vim,输入Sql语句查询之
2.通过数据库管理软件比如navacat连接上看看
3.通过idea使用Java偷窥
当涉及到复杂操作的时候,第一种和第二种的效率和准确率就难以保证了,所以我们还是用第三种,用JAVA!
用JAVA直接操作数据库的方式叫做JDBC,通过一系列手段,连接上数据库,然后完成各项操作
而mybatis就是替代JDBC功能的,为了连接一个表格,JDBC每次都要写一大段代码而且要重复很多遍,所以mybatis使用config.xml这个配置文件,写好参数配置以后
就不需要再重复了,随着mybatis功能的发展,config.xml文件采用configuration构架,并约定该构架需要配置以下元素节点:
properties 属性
settings 设置
typeAliases 类型别名
typeHandlers 类型处理器
objectFactory 对象工厂
plugins 插件
environments 环境
environment 环境变量
transactionManager 事务管理器
dataSource 数据源
databaseIdProvider 数据库厂商标识
mappers 映射器
初期只需要知道以下几个元素的功能:
typeAliases
首先,代码越多,BUG越多,为了减少BUG,应该少写点代码,特别是需要准确调用某个类的时候,必须使用完全限定名
(什么叫完全限定名?就是王二不是王二,是中国.山东.野鸡镇.爱新觉罗.王二)
代码多了容易BUG,所以我们就用王二带至后面那一串带小数点的名字,为了实现这个目的,所以我们在typeAliases中加入包名
表示这个包中所有的类的首字母小写的名字来作为它的别名,来指代它.
enviroments目前遇到的都是填的一样的内容,将来听说全部整合进spring,所以现在照着抄,将来spring的时候再学,或者不学了
数据源(dataSource)及后面的配置属性,就是为了连接数据库.其他的照着抄,抄多了再去翻官方文档的解释,目前统一是:POOLED
最后是映射器(mappers):通过这个让mybatis去他所指的位置去找SQL语句,所有的SQL语句都写在mappers所指向的内容里了
config.xml完
mapper.xml 映射文件
代码越多,BUG越多,为了减少BUG,少写代码,为了少写代码,除了不上班以外,就要靠轮子哥,扯远了
mybatis能节省大部分的SQL语句,就是将需要重复编写的SQL语句放在mapper.xml文件中了,整个xml文件就是为了里面的SQL语句服务的
为了服务好SQL语句,mapper.xml文件通过以下结构实现:
cache – 给定命名空间的缓存配置。
cache-ref – 其他命名空间缓存配置的引用。
resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
parameterMap – 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
sql – 可被其他语句引用的可重用语句块。
insert – 映射插入语句
update – 映射更新语句
delete – 映射删除语句
select – 映射查询语句
从最简单的配置开始:
<?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">
<!-- 上面几行全部为规定制式的头部文件,代笔xml文档的约定类型-->
<mapper namespace="com.how2java.pojo">
<select id="listCategory" resultType="Category">
select * from category_
</select>
</mapper>
属性1:namespace="com.how2java.pojo
表示命名空间是com.how2java.pojo,
在mybatis中,映射文件中的namespace是用于绑定Dao接口的,即面向接口编程。
当我们的namespace绑定接口后,我们就可以不用写接口实现类,mybatis会通过该绑定自动帮你找到对应要执行的SQL语句,如下:
假设定义了ListProductDAO接口
public interface ListProductDAO
{
List<Product> selectAllProducts();
}
对于映射文件如下:
<mapper namespace="ListProductDAO">
<select id="selectAllProducts" resultType="product">
SELECT p.* FROM product_ t WHERE t.type = '1' ORDER BY t.createtime DESC
</select>
请注意接口中的方法与映射文件中的SQL语句的ID一一对应 。
则在代码中可以直接使用ListProductDAO面向接口编程而不需要再编写实现类。
属性2:select元素及对应sql语句
<select id="listProduct" resultType="Product">
select * from Product_
</select>
id:这条sql语句用"selectPerson" 进行标识以供后续代码调用。
resultType:是属于resultMap中的一种,当我们在映射文件中定义sql语句的时候,都会要求设置sql语句的输出结果类型.我们将sql语句
直接在mysql数库中运行会得到一个结果:比如一个表格\一行数据\修改了某条数据\跨表查询等,如果要把结果输出到java中,那么就需要将
sql语句的结果数据,按照我们需要的要求提取出来.
mybatis解决这个问题的方案是:给每一个对应的表格写一个对应的类,类中的属性就为对应的字段,然后将提取的数据,赋予给对应类实例化的对象
通过类\对象\方法来实现对数据库的操作.
那么提取的数据如何对应呢?
通过resultmap将对应的数据赋值给对应类的实例化对象
比如上文的
<select id="selectAllProducts" resultType="product">
SELECT p.* FROM product_ t WHERE t.type = '1' ORDER BY t.createtime DESC
</select>
翻译一下就是:将product_表格中的数据,输出为resultType指定的product类,如果该类的属性名与product_表格的字段名一致,则相应赋值,
如果列名和属性名没有精确匹配,可以在 SELECT 语句中对列使用别名(这是一个 基本的 SQL 特性)来匹配标签。
例如将SQL语句更改为:
<select id="selectUsers" resultType="User">
select
user_id as "id",//user_id为字段名, id为对应类的属性名,以下类似
user_name as "userName",
hashed_password as "hashedPassword"
from some_table
where id = #{id}
</select>
同样的可以通过以下语句实现增删改或者输出某一行数据\某些符合标准的数据,并通过对应类的实例化对象实现各项操作,对应的执行语句有区别,以下是
简单举例: 对应的映射文件中insert语句
<insert id="addCategory" parameterType="Category" >
insert into category_ ( name ) values (#{name})
</insert>
对应的调用方式
Category c = new Category();//新增实例化对象
c.setName("新增加的Category");//对象属性赋值
session.insert("addCategory",c);//通过session.insert调用addCategory对应的SQL语句实现新增
对应的映射文件中delete语句:
<delete id="deleteCategory" parameterType="Category" >
delete from category_ where id= #{id}
</delete>
对应delete语句的调用方式
Category c = new Category();
c.setId(6);//实例化对象属性赋值
session.delete("deleteCategory",c);//通过session.delete调用deleteCategory对应的SQL语句,对符合条件的数据进行删除造作
获取:对应的映射文件的select语句
<select id="getCategory" parameterType="_int" resultType="Category">
select * from category_ where id= #{id}
</select>
parameterType:传递的参数类型,可以缺省,默认为对应类,本例汇总表示#{id}的类型
对应的调用方式:
Category c= session.selectOne("getCategory",3);
//新建一个实体化类型的对象,通过selectOne调用getCategory方法,调用id=3的数据实例化对象
更改:对应的update语句
<update id="updateCategory" parameterType="Category" >
update category_ set name=#{name} where id=#{id}
</update>
parameterType:当传入数据为多参数的时候,以类型为参数传递,本例中没有resultType,因为如果要输出,通过其他语句输出
当多条件查询的时候,parameterType类型为map,对应的sql语句为:
<select id="listCategoryByIdAndName" parameterType="map" resultType="Category">
select * from category_ where id> #{id} and name like concat('%',#{name},'%')
</select>
因为是多个参数,而selectList方法又只接受一个参数对象,所以需要把多个参数放在Map里,然后把这个Map对象作为参数传递进去
//hashmap的每个属性由2个值组成,第一个值的类型为字符串,第二个值的类型为任意类
Map<String,Object> params = new HashMap<>();
//第一个键值对的键为id,值为3
params.put("id", 3);
第二个键值对的键为name,值为cat
params.put("name", "cat");
//通过selectList调用listCategoryByIdAndName对应的sql语句,传入参数类型为,hashMap集合params
List<Category> cs = session.selectList("listCategoryByIdAndName",params);
对应的调用语句为
Category c= session.selectOne("getCategory",3);//通过selectOne实例化对象
c.setName("修改了的Category名稱");//将对象的对应值进行修改,本次修改的是name
session.update("updateCategory",c);//通过session.update调用updateCategory,执行的对象匹配对象c属性
查询所有:对应的select语句
<select id="listCategory" resultType="Category">
select * from category_
</select>
返回参数类型为Category对象
对应的调用语句是
List<Category> cs = session.selectList("listCategory");
//通过session.selectList调用,返回对象为集合cs的元素
以上是最简单类型的CRUD操作,实际工作中应该会比这些复杂的多
resultType就是通过sql语句调用数据后输出的参数类型设置,一般对应类型占大多数情况.
resultType有自身的局限性:
1.resultType表示返回类型,映射成我们的model对象中的实体当返回类型是resultType时,MyBatis会将Map里面的键值对取出赋给resultType所指定的
对象对应的属性。所以其实MyBatis的每一个查询映射的返回类型都是ResultMap,只是当提供的返回类型属性是resultType的时候,MyBatis
自动的给把对应的值赋给resultType所指定对象的属性。
2.如果对应的属性名称与字段名称匹配不上,则属性值会为空
3.对于复杂的多字段\多表格查询,通过resultType实现会很麻烦,因为无法设定属性与值的对应关系,自动生成会导致很多空值或者意料外的值,为了避免这个情况
需要写很多的sql语句,而且对于同一个方法,不同的对象调用的时候,都要重新书写sql语句的类容,不方便使用.
为了避免resultType的局限性,使用resultMap来实现属性名称与表格值对应的问题(键值对应关系的设定)
实际上resultType也是resultMap中的一种,只不过是特殊的一种,不需要设定键值对的一种,会自动匹配数据和属性的对应值,会自动用字段名称去搜索对应类的
属性名称,并自动赋值.
而返回对象是resultMap类型时,因为Map不能很好明确的指向对象的某个属性,就需要自己再进一步的把它转化为对应的对象,这常常在复杂查询中很有作用。
那么resultMap是如何解决复杂查询的呢,有以下三种复杂查询,分开说
1.所有的resultType类型都可以用resultMap替代
例如以下resultType类型,转换成对应的resultMap类型,需要转换两处,一处是定义输出类型,另一处是调用输出类型的SQL语句出
定义输出类型
<select id="selectUsers" resultType="User">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
对应sql语句
<select id="selectUsers" resultType="User">
select
user_id as "id",
user_name as "userName",
hashed_password as "hashedPassword"
from some_table
where id = #{id}
</select>
修改成resultMap形式,定义处
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
sql语句调用处
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
通过转换,很好的体现了二者的区别:
返回对象是resultType时,要求sql语句输出的字段名字必须和属性名字一样,所以sql语句会因此多一些
resultMap,需要设置好属性名称与对应值的关系,才能使用,即使两者本身一样,也需要设置了来
所以resultType适合简单的数据处理,而resultmap更适合复杂一些的数据处理
2.resultMap通过collection\association实现一对多\多对一\多对多查找
如何准确定义一对多\多对一\多对多?
resultmap是如何通过collection以及association实现的?
多对多查询的时候collection如何嵌套association的?嵌套的关系如何来的?
如果是两张表进行多对多,如何实现?
三张和多张呢?
什么情况下使用association,什么情况下使用collection?
3.首先sql语句返回的结果有两种:reusltType,resultMap
复杂数据处理情况下,优先选择resultMap,定义resultMap由以下几个元素组成
<resultMap type="Order" id="orderBean">
<id column="oid" property="id" />
<result column="code" property="code" />
<collection property="orderItems" ofType="OrderItem">
<id column="oiid" property="id" />
<result column="number" property="number" />
<association property="product" javaType="Product">
<id column="pid" property="id"/>
<result column="pname" property="name"/>
<result column="price" property="price"/>
</association>
</collection>
</resultMap>
<resultMap type="Order" id="orderBean">
type:type 类的完全限定名, 或者一个类型别名,返回的数据全部对应赋值到该类中,该类在一对多\多对一\多对多情况下,需要的属性值和方法各不相同
id:方便后续调用
<id column="oid" property="id" />
在每次定义resultMap时,都要定义对应的id,据说可以提高整体性能
<result column="code" property="code" />
propery中填入对应类的属性名,则将column中对应的字段值赋予该属性.
resultMap\collection\association
三者的功能其实都是一致的,只是嵌套的顺序或者说内外层不同,用来针对类的结构
例如类结构如下:
public class A{
private B b1;
List<B> b2;
}
同时另外有
Public class B{
private C c1
List<C> c2
}
同时有对应的表格A_ B_ C_三张表,如何对应呢,就是类A的属性名称与表A的字段名称一致
(即使不一致,也可以提取数据以后通过<result column="Acode" property="Bcode" />,来引导对应一致)
resultmap的对应输出类型为A
association的输出类型对应b1
collection的输出类型对应为b2
如果需要关联第三张表D,则根据第三张表与AB的关系来确定嵌套方式如果与b1\b2类似,则采用对用的方式
如果表D与c1或者c2的情况类似,则需要对应的嵌套循环:例如
<collection property="orderItems" ofType="OrderItem">
<id column="oiid" property="id" />
<result column="number" property="number" />
<association property="product" javaType="Product">
<id column="pid" property="id"/>
<result column="pname" property="name"/>
<result column="price" property="price"/>
</association>
</collection>
与c1效果一致,类似的将上面的association替换成collection则表示c2
那么ABC表的关系如何在类中表达?
什么情况下是Ab1,什么情况下是Ab2,什么情况下是Ab1c1,什么情况下是Ab1c2\Ab2c1\Ab2c2,以上6总关系的循环重复使用,应该可以概括所有多表关系
Ab1:类A的一个对象a1,对应B类的一个对象b1,如果每个a1对应不同的b1,则为一对一,如果多个不同的a1对象,可以对应同一个b1对象,则为多对一.
比如B类只有2个实例化对象,b1\b2,A的实例化对象有2个或者2个以上a1\a2\a3\a4,如果a1对应b1,同时a2对应b2,且A类只有a1\a2两个对象,则两者
关系就是一对一,其实完全可以合并成一个表,因为键值对应,这个时候的查询结果就是一对一查询,通过Ab1来表达,
如果A类有多个对象,a1\a2\a3\a4等,对应的B类,关系为:a1-->b1,a2-->b1,a3-->b2,a4-->b2;类似这种多对一关系的,比如,一个产品A有属于品类B,
订单A来自于用户B,类似情况适用于Ab1的方式来表达.就是做个不同的A的对象,对应同一个B的对象.
Ab1:多个a对象对应同一个b对象;
相反,如果多个B类的对象,对应同一个A类对象,就用Ab2来表达,比如不同的订单B属于不同的用户A,但是每个订单b1只能属于一个用户a1,不同的订单也可能属于
同一个用户,或者同一个用户有多个订单;或者类似于不同产品B属于不同品类A,但是一个产品b1只属于某一种品类a1,多个产品可能都属于同一个品类.
类似的关系,我们定义为一对多,用Ab2才能表达:
Ab2:一个类的对象里面包含另一个类的某个集合比如品类A与产品B,用户A与订单B的关系.
类似的
Ab1c1:比较少见,相当于订单A与用户B与地区C的关系:多个订单对应多个用户对应多个地址,但是一个订单只有一个用户一个地区,多个订单可能属于多个用户,
也可以属于一个用户,但不能同时属于两个及以上用户,一个用户属于一个地区,多个用户属于多个地区,但是多个用户可能属于同一个地区或者不同地区.最后订单A
和区域C也是多对一关系.可以用Ab1的关系来形容A与C的关系,即:一个订单只发往一个地区,多个订单可能发往一个地区或者多个地区.
Ab1c2:三者关系需要通过b1表来确定,比如订单表和产品表,各自不相干,为了表达一个订单有多个产品,每种产品属于不同的订单,这种关系的时候,需要使用到
Ab1c2,实际上是通过两者的中间表B表达的是A与C的多对多关系,才有有效,否则
Ab2c2,是需要其他表格辅助进行定义的,否则无法表达三者关系,三者都是相互之间多对多,无法判定A与C的关系
Ab2c1:比如用户A有多个订单B,同时每个订单有一个支付方式,多个订单可能使用一个支付方式,或者多个支付方式,但是一个订单不可能多个支付方式
(假设是这样,实际可以组合支付)
那么用户A通过订单B,实现对应的支付方式,但是如果没有订单,则没有支付.同时有用户才有支付,无用户无支付.无订单,无支付.
这就是Ab2c1.
有效关系就是三种:Ab1 Ab2 Ab1c2,重点是AB1C2这种关系
也就是多对一,然后一对多,比如订单A与用户B,以及用户B与银行卡C,那么订单A与银行卡C没有任何关系
而如果要表达多对多关系,就一定需要中间表,且这个中间表满足:
比如订单表和产品表,就是多对多,一个订单有多种产品,每种产品属于多个订单,或者用户和家电的关系,一个用户有多种家电,每种家电有很多用户
而中间表,为了表示两表关系:与各表关系是多对一,还是一对多?
比如订单表与产品表的中间表:假设中间表内容为
订单1对应产品 1,2,3,
订单2对应产品 2,3,4
那么中间表和订单表,以及产品表的关系是什么?比如订单表和中间表,没有任何关系,因为中间表就是为了定义订单表和产品表的关系而创建的,根据多对多关系
中间表的字段值,是键值对应关系,比如订单a中的产品b构成了中间表id=n的数据,加上其他辅助描述字段,比如数量c,abc三个值就构成了中间表id=n的数据
那么订单表与中间表,或者产品表与中间表,是一对多或者多对一或者多对多的关系吗?
首先订单表和中间表示单边关系而不是双边关系,没有中间表,订单表同样成立,而没有订单表,中间表就不存在了,订单表的某些值更改,中间表的值对应更改,但是
中间表的值更改,并不会导致订单表的更改.
所以
如果要表达三表及多表关系,一定只能是表达两表关系,而不能直接表达三表或多表关系
一对多,类结构:AB2,用association来表达
多对一,类结构:AB1,用collection来表达
多对多,类结构:AB2,BA1C1
对应的映射结构为
mapperA
resultmap a
collection b
association c
</collection>
mapper b
无内容,是否可以省略?
mapper c
无内容,是否可以省略?
或者让两者的配置文件内容只有开头,其余为空?
总结:
association返回值类型为属性值和单个的对象
collection返回值的类型为对象集合,需要定义对象集合的对象类型,然后给每个对象进行对应赋值
试用的情况:简而言之
Mybatis中 collection 和 association 的区别
public class A{
private B b1;
private List<B> b2;
}
在映射b1属性时用association标签,(一对一的关系)
映射b2时用collection标签(一对多的关系)
多对多的关系按照公式去套
多对多,类结构:AB2,BA1C1,C
对应的映射结构为
mapperA
resultmap a
collection b
association c
</collection>
mapper b
无内容,是否可以省略?或者为resultType?
mapper c
无内容,是否可以省略?或者为resultType?
需验证上文猜想
试试取消掉本项目中的category类
步骤如下:
1.取消config中关于category.xml的mapper映射
2.修改product.xml中与category有关的内容,包括association相关,select语句相关3.可选项:取消掉product类中与category相关的内容
找了很久的mybatis3的bug找到了
public void setTeacher(Teacher teacher) {
this.teacher=teacher;
}
(Teacher teacher)中teacher拼写错误了,导致,p.getTeacher()输出为空.
为什么setTeacher的bug会导致getTeacher无法正确输出呢?这是为什么?
难道在生成teacher类的时候,调用了setTeacher方法吗?
这个过程是什么样子的,为什么这个BUG会导致那个错误?
首先问题是:在mybatis3中,因为Application类中,setTeacher方法,传入参数的类型拼写错误,导致测试类执行以后,对应的teacher表的数据
输出为空,也就是teacher对应的类没有生成或者至少没有赋值
那么到底是没有生成对应的类,还是生成了对应的类但是没有成功赋值
马上实验,取消对toString的override,还原bug,看输出结果:输出为null,将bug调试正确,然后toString方法不重写,保留刚刚的效果,
输出结果为对应类的内存地址:com.how2java.pojo.Teacher@3cbbc1e0
说明setTeacher的方法不正确,导致的问题是Application中没有生成对应的teacher类,而不是生成了没有对属性赋值
为什么在Application中生成teacher类,要通过setTeacher的方法呢?是不是在类中生成同包的其他类,必须通过set方法呢?
用两个关联类来做实验
1.建立普通类 ApplicationTest 和 TeacherTest,分别定义各自的属性(private)为aid int,aname String,先不设置对应属性的set,get
2.在AT中定义TT类,private TT tT,没有set和get方法
3.在AT中main函数下,生成一个teacher对象,不赋值,直接输出,看输出结果
4.增加Setget 方法,仅限对AT类中的private TT tT属性,再看输出结果,能否给TT对象的属性赋值?
5.增加所有属性的Setget方法,在AT类中,给TT类的实例化对象赋值并输出遇到的问题:
mybatis实例化类的具体过程还是没掌握,将来有机会再慢慢补上了
今天的收获:
对于多对一\一对多\多对多,以及resultType\resultMap\collection\association有了充分认识
明天的计划
完成mybatis剩余部分内容的学习:注解\动态SQL\mybatis的其他事项
评论