目录
一:主键回写
二:传递参数再探
二:返回结果封装
三:动态sql
四:Mybatis的多表操作
五:延迟加载
六:Mybatis缓存
七:通用mapper
好记性不如烂笔头,很久不碰真的会忘,本篇开始重新梳理一下Mybatis的内容,方便以后查询。
代码:GitHub中mybatis分支
目录
一:主键回写
二:传递参数再探
二:返回结果封装
三:动态sql
四:Mybatis的多表操作
五:延迟加载
六:Mybatis缓存
1:在不需要回写主键之前插入数据的mapping内容如下:
INSERT INTO t_product (product_name,stock,price,note,VERSION) VALUES(#{productName},#{stock},#{price},#{note},#{version})
需要回写主键的时候需要添加一个新标签:selectKey。keyColumn为数据库中主键,keyProperty对应实体类字段.resultType为实体类中字段什么类型,order表示是插入之前获取还是插入之后获取。这样我们在传入产品VO对象执行完后,对象的主键属性就会被自动赋值。
SELECT LAST_INSERT_ID()
INSERT INTO t_product (product_name,stock,price,note,VERSION) VALUES(#{productName},#{stock},#{price},#{note},#{version})
1:传递简单的参数。这种方式不用多说了,在标签上有一个parameterType属性写上对应的基本数据类型即可。
2:传递pojo对象参数。
Mybatis中使用ognl表达式解析对象的值,ognl(Object Graphic Navigation Language),它是通过对象的取值方法来获取数据。在写法上把get去掉了。比如:类中 user.getUserName();在ognl中是user.userName,但是我们在mapper.xml中的sql语句中直接使用的是${属性名}那是因为标签属性parameterType已经定位到类了。
3:传递对象中的对象作为参数。正如我们上面所说的Mybatis中使用ognl表达式来获取属性,比如我们现在有个User类中有个Product对象属性,Product中有个productName属性,我们想用productName属性来作为查询条件,但是我们向后传入参数对象为为User对象,这个时候我们需要按照如下方式获取productName属性。#{product.productName}
1:基本数据类型和对象属性与数据库字段匹配的可以直接使用resultType这个属性来设置。
2:对象属性和数据库字段匹配不上的第一种方式可以在sql语句里起别名,还有一种方式就是使用resultMap属性。定义如下所示:
然后在select等标签里不再使用resultType而是用resultMap。
1:
参数使用是一个pojo对象在mapper里面使用如下标签内容。test属性用来设置条件的。如果多个条件用 and连接。注意属性里面的用的是属性里面的字段。where标签自动移除第一个and。
2:foreach标签。对于查询条件中使用 id in ()的时候需要使用此标签。
(1)比如下面的例子:如下collection中是对象属性中获取的集合。
java中的调用如下:
public List getByProductIds(ProductPo productPo);
mapper中内容如下:ids为产品类中的集合属性名。
(2)如果参数直接传入是集合:collection中设置就是list. 注意这时候java中调用时候的参数名可以任意。
java中调用如下:
public List getProductByList(ArrayList ids);
mapper中内容如下:
(3):集合中如果参数传入的是个数组。collection就是array.注意这时候java中调用时候的参数名可以任意。
java中调用如下:
public List getProductByArray(Long[] ids);
mapper中的内容如下:
collection:代表要遍历的集合元素,注意编写时不要#{}
open:代表语句的开始部分
close:代表结束部分
item:代表遍历集合的每个元素,生成的变量名,可以自定义
seperator:代表分隔符
使用的时候要用 ${生成的变量名}
3:set标签
set 标签是用在更新操作的时候,功能和 where 标签元素差不多,主要是在包含的语句前输出一个 set,然后如果包含的语句是以逗号结束的话将会把该逗号忽略,如果 set 标签最终返回的内容为空的话则可能会出错(update table where id=1)
使用set标签示例:
update products
citycode = #{cityCode} ,
name = #{Name} ,
description = #{Description} ,
where id =#{id}
4:trim标签
trim 元素的主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某些后缀,与之对应的属性是 prefix 和 suffix;可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,对应的属性是 prefixOverrides 和 suffixOverrides;正因为 trim 有这样的功能,它可以用来实现 where 和 set 的效果。
前面where标签示例,此处使用trim代替:
代替set内容
update products
citycode = #{cityCode} ,
name = #{Name} ,
description = #{Description}
where id=#{id}
5:choose (when, otherwise)标签
choose标签是按顺序判断其内部when标签中的test条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when 的条件都不满则时,则执行 otherwise 中的sql,类似于sql server语句(case when then)
如果name!=null,则解析出sql: select * from product where name like ?
Name==null&& description!=null,则解析出sql: select * from product where description like ?
否则:select * from product where unitprice >?
(1)一对多,和多对一的表操作问题。比如一个用户对应多个账单,一个账单对应一个用户。因此我们在建立实体类的时候需要在用户实体类下面建立一个账单的List集合。而在账单实体类中建立一个用户对象。
1:完成用户的一对多操作。
用户实体类: 账户实体类:
表中数据如下:
用户信息: 账户信息:
我通过查询用户信息把用户相关的账户信息也查询出来。
userMapper中使用下面方法:
public User getUserAccount(String userName);
mapper.xml中使用resultMap来进行设置一对多映射,
resultMap中的 id属性:标识唯一性,自定义。 type属性:起了别名的实体类或全路径实体类名。
collection用来配置集合属性: property :实体类中集合的属性名,oftype:集合包含的实体类别名或全路径实体类名。
然后进行查询得到的结果如下:
看起来挺正常,张三的两个账户信息都出来了。但是注意,上面resultMap中的collection下的主键
我就改了一下id中的column,其他没变,然后执行查看结果:
是不是很诧异,返回结果是user中和account中id为1的查询出来了,这就是问题所在,所以在多表查询的时候如果出现字段重名,一定要起别名。不然出现意想不到的错误。
2:查询账户实现一对一查询。数据还是上面的数据。我们账户数据的同时把账户对应的用户信息也查询出来。
账户实体类中有一个用户的对象属性。
accountService中调用方法如下:
public List getByUid(Integer uId);
accountmapper的配置如下:注意一对一这里使用:
请求的结果如下:
我把同名的字段不使用别名看会得到什么结果,mapper.xml中设置如下,就把别名去掉。
得到的结果如下:
看到出有什么不一样了吗?每个账户对应的用户id和账户id是一样的,这显然是不对的。和第一中情况一样,这里它把user的id当成了账户的id了。所以再强调一遍,多表查询的时候重复字段一定要起别名。
(2)多对多查询。我建立了用户,角色关系来表示多对多的关系。两个表数据如下:
用户: 角色: 中间表:
用户的实体类:
角色的实体类:
我现在查询角色信息,把角色有关的用户信息一块查询出来。
roleMapper.xml中配置的信息如下:
java中调用是直接调用没有传参:
@Repository
public interface RoleMapper {
public List getRoleAll();
}
返回结果如下:
角色的id为空,因为sql里面我没有写出所以为空,用户的id我写出了,所以正常返回了。但是这里还要注意一下mapper里面我对user中的id起了别名,sql和resultMap里面都起了而且对应的。我们试下如果不对应上看下什么结果。
mapper里面的内容改成如下内容:
结果如下:
很明显和一对多的那种情况一样,user下面的id当成了role中的那个id,没有赋值返回,所以为空。
根据用户查询角色的信息和上面雷同,就不再列出了。
我们都知道在使用Mybatis的时候会用到延迟加载也就是懒加载或者立即加载。对于数据之间的关系我们可以分为:
一对一,多对一,一对多,多对多。在使用的时候我们一般按,如果是关联的一,就使用立即加载。如果关联的是多,就使用懒加载。
为了能看到查询的内容,我们配置一下sql打印的功能在application.properties中配置:
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
1:我们先来看下一对一的延迟加载。
账户信息和用户信息是多对一,但是考虑一条账户信息就对应一个用户。我们查询账户信息的时候懒加载用户信息来看下怎么做。
由于我们不要查询账户信息的时候直接得到用户信息,所以上面所用的resultMap里面的内容不需要全部一一映射了,accountMapper.xml先改为如下内容:
我们在accountMapper中调用方法如下:
@Repository
public interface AccountMapper {
public List getAllAccounts();
}
我们执行一下结果:
发现查询完账户信息的时候立马就去查询了用户信息。这是因为Mybatis默认时立即加载的,所以需要配置一下,把懒加载设置为true。
在application.properties中配置:
mybatis.configuration.lazy-loading-enabled=true
再执行结果:
看,就只是执行查询账户的sql没有去查询用户信息。假如我们现在要根据这些查询出的账户信息来得到用户信息能不能得到呢?
比如我们在account实体类中重写toString方法,这样在toString的时候就会把账户对应的用户信息也打印出来了,我们在controller调用如下:
@GetMapping("/getAllAccounts")
public String getAllAccounts(){
return accountService.getAllAccounts().toString();
}
页面结果:
控制台结果:
看到用户信息是后面又建立连接来查询的。
2:一对多延迟加载。我们现在查询用户信息延迟加载对应的账户信息。
其实延迟加载就是sql语句不再关联连表查询而是查单表,通过配置调另外一张mapper中的配置。
userMapper.xml中配置如下:
select ID,user_name as userName ,sex from t_user
调用后后台输出:
只时查询了用户信息。
我们把查询出来的用户通过toString()的方法打印出来看下什么结果:账户信息时后面再去查询的。
1:一级缓存。默认开启。
它指的是Mybatis中SqlSession对象的缓存。当我们执行查询之后,查询的结果会存入到SqlSession为我们提供的一块区域内,该区域为一块Map。当我们再次去查询同样的数据,mybatis会先去SqlSession中查询,有的话直接使用。当sqlSession消失时,缓存也会消失。当然也可以手动清除一级缓存。sqlSession对象有个clearCache()方法。针对的是同一个sqlSession对象来说。
但是我们整合SpringBoot之后一级缓存已经失效了,因为sqlSession已经交给了Spring来管理,每次查询都会使用新的的sqlSession对象,因此都会查询。
2:二级缓存。
它指的是Mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory创建的SqlSession共享其缓存。
使用二级缓存需要手动开启配置。
1:配置缓存开启:
mybatis.configuration.cache-enabled=true
2:让当前配置文件支持二级缓存。
在mapper.xml中配置:
3:让当前操作支持二级缓存。在
代码中测试如下:
@GetMapping("/getUser")
public String getUser(){
User nmae= userService.getByUserNmae("张三");
User nmae1= userService.getByUserNmae("张三");
System.out.println(nmae==nmae1);
return "成功";
}
控制台输出内容:
可以看到只有一次查询sql的输出,但是我们发现两个对象比较输出了false。这是因为,二级缓存中存的只是数据而不是对象。也就是第二次从缓存中得到的数据然后重新生成对象返回,所以两次返回对象不一样。
mybatis中我们对于单表的操作,无非就是增删改查,每次新建表之后都要创建一个mapper接口写上增删改查的方法,然后在mapper.xml中写上对应的sql。这样很繁琐,于是一个大牛就写了一个通用mapper.对于单表的操作只要继承通用mapper接口就可以了,不需要xml的配置。这位大牛还把自己写成了集成SpringBoot的内容,我们拿来用.
通用mapper的github-wiki地址: 通用mapper文档
1:引用pom坐标:
tk.mybatis
mapper-spring-boot-starter
RELEASE
2:写mapper接口:注意Mapper包名。
package com.mystore.mapper;
import com.mystore.entity.User;
import tk.mybatis.mapper.common.Mapper;
/**
* Created by FireCode on 2020/2/15.
*/
public interface UserGeneMapper extends Mapper{
}
在idea中Alt+7 可以看到自定义接口集成来了很多方法:
3:使用了通过mapper之后,启动类上的MapperScan注解也需要改为通用mapper中的MapperScan.,原来的注解是
import org.mybatis.spring.annotation.MapperScan;
改为如下: 注意参数是我们mapper接口所在的类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan("com.mystore.mapper")
public class MystoreApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(MystoreApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(MystoreApplication.class, args);
}
}
3:为了告诉通用mapper该怎么根据实体类找到对应的表,我们还要对实体类进行添加一些注解。
@Table(name = "T_USER")
public class UserVo implements Serializable {
@Id
@GeneratedValue(generator = "JDBC")
private Integer id;
@Column(name = "user_name")
private String userName;
private String sex;
@Transient
private List roles;
}
对于注解的说明如下:
1: 表名默认使用类名,驼峰转下划线(只对大写字母进行处理),如UserInfo默认对应的表名为user_info。
2:表名可以使用@Table(name = "tableName")进行指定,对不符合第一条默认规则的可以通过这种方式指定表名.
3:字段默认和@Column一样,都会作为表字段,表字段默认为Java对象的Field名字驼峰转下划线形式.
4:可以使用@Column(name = "fieldName")指定不符合第3条规则的字段名
5:使用@Transient注解可以忽略字段,添加该注解的字段不会作为表字段使用.
6: 建议一定是有一个@Id注解作为主键的字段,可以有多个@Id注解的字段作为联合主键.
7:如果是MySQL的自增字段,加上@GeneratedValue(generator = "JDBC")即可,还可返回自增长的主键。
对于通用mapper中的一些方法说明如下:
目录
一:主键回写
二:传递参数再探
二:返回结果封装
三:动态sql
四:Mybatis的多表操作
五:延迟加载
六:Mybatis缓存
七:通用mapper
Select
方法:List select(T record);
说明:根据实体中的属性值进行查询,查询条件使用等号
方法:T selectByPrimaryKey(Object key);
说明:根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号
方法:List selectAll();
说明:查询全部结果,select(null)方法能达到同样的效果
方法:T selectOne(T record);
说明:根据实体中的属性进行查询,只能有一个返回值,有多个结果是抛出异常,查询条件使用等号
方法:int selectCount(T record);
说明:根据实体中的属性查询总数,查询条件使用等号
Insert
方法:int insert(T record);
说明:保存一个实体,null的属性也会保存,不会使用数据库默认值
方法:int insertSelective(T record);
说明:保存一个实体,null的属性不会保存,会使用数据库默认值
Update
方法:int updateByPrimaryKey(T record);
说明:根据主键更新实体全部字段,null值会被更新
方法:int updateByPrimaryKeySelective(T record);
说明:根据主键更新属性不为null的值
Delete
方法:int delete(T record);
说明:根据实体属性作为条件进行删除,查询条件使用等号
方法:int deleteByPrimaryKey(Object key);
说明:根据主键字段进行删除,方法参数必须包含完整的主键属性
Example方法
方法:List selectByExample(Object example);
说明:根据Example条件进行查询
重点:这个查询支持通过Example类指定查询列,通过selectProperties方法指定查询列
方法:int selectCountByExample(Object example);
说明:根据Example条件进行查询总数
方法:int updateByExample(@Param("record") T record, @Param("example") Object example);
说明:根据Example条件更新实体record包含的全部属性,null值会被更新
方法:int updateByExampleSelective(@Param("record") T record, @Param("example") Object example);
说明:根据Example条件更新实体record包含的不是null的属性值
方法:int deleteByExample(Object example);
说明:根据Example条件删除数据