mybatis--使用
一、入门程序
直接上代码
sql:
CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `username` varchar(20) NOT NULL COMMENT '用户名', `birthday` date DEFAULT NULL COMMENT '生日', `sex` varchar(30) DEFAULT NULL COMMENT '性别', `address` varchar(10) DEFAULT NULL COMMENT '地址', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=3687 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; INSERT INTO `user`(`id`, `username`, `birthday`, `sex`, `address`) VALUES (1, 'lcl', '2020-11-23', '男', '北京'); INSERT INTO `user`(`id`, `username`, `birthday`, `sex`, `address`) VALUES (2, 'lcl', '2020-11-23', '男', '北京'); INSERT INTO `user`(`id`, `username`, `birthday`, `sex`, `address`) VALUES (3, 'qmm', '2020-11-23', '女', '北京');
pom文件
<!-- mybatis依赖 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <!-- mysql依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.35</version> </dependency> <!-- 单元测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency>
db.propertoes
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://****** jdbc.username=******* jdbc.password=*******
UserMapper
<?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="test"> <!-- 根据id获取用户信息 --> <select id="findUserById" parameterType="int" resultType="com.lcl.galaxy.mybatis.common.domain.UserDo"> select * from user where id = #{id} </select> </mapper>
SqlMapperConfig
<?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="db.properties"></properties> <!--配置环境--> <environments default="mysql"> <!-- 配置mysql的环境--> <environment id="mysql"> <!-- 配置事务 --> <transactionManager type="JDBC"></transactionManager> <!--配置连接池--> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </dataSource> </environment> </environments> <!-- 配置映射文件的位置 --> <mappers> <mapper resource="mapper/UserMapper.xml"></mapper> </mappers> </configuration>
UserDo
package com.lcl.galaxy.mybatis.common.domain; import lombok.Data; import java.util.Date; @Data public class UserDo { private int id; private String username; private Date birthday; private String sex; private String address; }
UserDao
package com.lcl.galaxy.mybatis.demo.dao; import com.lcl.galaxy.mybatis.common.domain.UserDo; import java.util.List; public interface UserDao { public UserDo findUserById(int id) throws Exception; }
UserDaoImpl
package com.lcl.galaxy.mybatis.demo.dao.impl; import com.lcl.galaxy.mybatis.common.domain.UserDo; import com.lcl.galaxy.mybatis.demo.dao.UserDao; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import java.util.List; @Slf4j public class UserDaoImpl implements UserDao { private SqlSessionFactory sqlSessionFactory; public UserDaoImpl(SqlSessionFactory sqlSessionFactory){ this.sqlSessionFactory = sqlSessionFactory; } @Override public UserDo findUserById(int id) throws Exception { SqlSessionFactory sqlSessionFactory = this.sqlSessionFactory; SqlSession sqlSession = sqlSessionFactory.openSession(); UserDo userDo = null; try { userDo = sqlSession.selectOne("test.findUserById",id); }finally { sqlSession.close(); } return userDo; } }
测试类
package com.lcl.galaxy.mybatis; import com.lcl.galaxy.mybatis.common.domain.UserDo; import com.lcl.galaxy.mybatis.demo.dao.UserDao; import com.lcl.galaxy.mybatis.demo.dao.impl.UserDaoImpl; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import java.io.InputStream; import java.util.List; @Slf4j public class demoTest { private SqlSessionFactory sqlSessionFactory; @Before public void init() throws Exception { SqlSessionFactoryBuilder sessionFactoryBuilder = new SqlSessionFactoryBuilder(); InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); sqlSessionFactory = sessionFactoryBuilder.build(inputStream); } @Test public void testFindUserById() throws Exception { UserDao userDao = new UserDaoImpl(sqlSessionFactory); UserDo user = userDao.findUserById(2); log.info("userDo====[{}]",user); } }
这里简单说一下Mapper.xml文件,每一个文件都有一个nameSpace,每一个sql语句都有一个id,因此在查询时使用nameSpace+id就定位到了唯一一条sql;
sql语句可以使用select、insert、update、delete标签来作为sql类型标记,parameterType是参数类型,resultType是出参类型。对于参数传递,可以使用#{}来进行传递,如果入参是简单类型(8中基本类型+String类型),#{}内可以随便写内容,否则要与入参一直;如果入参是对象,则参数名称要与对象的属性名称保持一致;如果返回值为对象,则查询字段要和对象属性保持一致。
二、基础应用
(一)Mapper代理开发模式
在入门程序中,每一个实现类(例如UserDaoImpl)都要自己获取SqlSessionFactory以及SqlSession,且自己需要写impl实现类,对于mybatis,其提供了基于动态代理的Mapper开发模式。
针对Mapper开发对象可以使用Xml模式和注解模式
1、Xml方式
针对Xml开发模式,只需要写Mapper接口和Mapper映射对象,不需要编写实现类,其中Mapper接口就是之前的Dao对象。
对于开发时,需要遵循如下接口:
1、Mapper映射文件的nameSpace要与Mapper接口的类路径一致
2、Mapper映射文件的Statement的id要与Mapper接口中的方法名保持一致
3、Mapper映射文件的ParameterType要与Mapper接口中方法的入参保持一致
4、Mapper映射文件的ResultType要与Mapper接口中的返回值保持一致
接下来,就直接上代码:
新增UserMapper接口,内容同UserDao一致
package com.lcl.galaxy.mybatis.anno.mapper; import com.lcl.galaxy.mybatis.common.domain.UserDo; import java.util.List; public interface UserMapper { public UserDo findUserById(int id) throws Exception; }
新增UserMapper.xml文件,其中nameSpace为上述UserMapper接口的类路径
<?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.lcl.galaxy.mybatis.anno.mapper.UserMapper"> <!-- 根据id获取用户信息 --> <select id="findUserById" parameterType="int" resultType="com.lcl.galaxy.mybatis.common.domain.UserDo"> select * from user where id = #{id} </select> </mapper>
在SqlMapperConfig中新增上述xml配置
<mapper resource="mapper/anno/UserMapper.xml"></mapper>
新增测试类
由于UserMapper接口没有实现类,如何才能对其初始化调用其方法?其实,Mybatis在SqlSession接口中提供了一个getMapper方法,入参是Mapper接口类,返回值就是Mapper接口对象,那么就可以如此获得UserMapper对象,然后直接调用其提供的方法。
package com.lcl.galaxy.mybatis; import com.lcl.galaxy.mybatis.anno.mapper.UserMapper; import com.lcl.galaxy.mybatis.common.domain.UserDo; import lombok.extern.slf4j.Slf4j; 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.InputStream; import java.util.List; @Slf4j public class AnnoTest { private SqlSessionFactory sqlSessionFactory; @Before public void init() throws Exception { SqlSessionFactoryBuilder sessionFactoryBuilder = new SqlSessionFactoryBuilder(); InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); sqlSessionFactory = sessionFactoryBuilder.build(inputStream); } @Test public void testFindUserById() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserDo user = userMapper.findUserById(2); log.info("userDo====[{}]",user); } }
(二)使用注解方式
使用注解模式,只需要编写Mapper接口就OK了,但是在接口的每一个方法上都需要使用mybatis提供的注解,例如@Select、@Insert、@Update、@Delete
新增Mapper接口
package com.lcl.galaxy.mybatis.anno.mapper; import com.lcl.galaxy.mybatis.common.domain.UserDo; import org.apache.ibatis.annotations.Select; import java.util.List; public interface UserAnnoMapper { @Select("select * from user where id = #{id}") public UserDo findUserById(int id) throws Exception; }
新增测试类
package com.lcl.galaxy.mybatis; import com.lcl.galaxy.mybatis.anno.mapper.UserAnnoMapper; import com.lcl.galaxy.mybatis.common.domain.UserDo; import lombok.extern.slf4j.Slf4j; 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.InputStream; import java.util.List; @Slf4j public class AnnoTest { private SqlSessionFactory sqlSessionFactory; @Before public void init() throws Exception { SqlSessionFactoryBuilder sessionFactoryBuilder = new SqlSessionFactoryBuilder(); InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); sqlSessionFactory = sessionFactoryBuilder.build(inputStream); sqlSessionFactory.getConfiguration().addMapper(UserAnnoMapper.class); } @Test public void testFindUserById() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); try{ UserAnnoMapper userMapper = sqlSession.getMapper(UserAnnoMapper.class); UserDo user = userMapper.findUserById(2); log.info("userDo====[{}]",user); }finally { sqlSession.close(); } } }
这里需要注意一点,该测试类与xml基本上一致,只新增了一行代码:sqlSessionFactory.getConfiguration().addMapper(UserAnnoMapper.class);因为这一次并没有新增xml文件,因此加载的SqlMapperConfig中也没有加载到xml,因此需要使用addMapper方法加载,否则会出现加载不到xml错误:sqlSessionFactory.getConfiguration().addMapper(UserAnnoMapper.class);
(二)全局配置文件
对于SqlMapperConfig.xml中配置的内容和顺序如下(顺序必须按照如下进行):
properties(属性配置)、settings(全局配置参数)、typeAliases(类型别名)、typeHandlers(类型处理器)、objectFactory(对象工厂)、plugins(插件)、environments(环境属性对象集合)、environment(环境对象属性)、TransactionManager(事务管理)、dataSource(数据源)、mappers(映射器),对于以上对象,下面将一一描述。
1、properties标签
properties标签主要是用来加载配置文件的,可以有两种写法,第一种是直接使用外部配置文件,第二种是直接在该标签中使用property标签来设置属性
propertites标签使用方式 方式一:使用resource加载外部配置文件 <properties resource="db.properties"></properties> 方式二:使用property子标签配置 <properties> <property name="jdbc.username" value="*******"/> </properties>
同时这里需要说明一下,如果同时使用了这两种配置方式配置了一个参数,则外部的配置参数会覆盖property子标签的值,因为mybatis加载SqlMapperConfig时会先加载property子标签的内容,加载完毕后再加载外部配置文件的参数,因此外部文件的配置会将property子标签的值给覆盖掉。
2、typeAliases标签
typeAliase标签用来简化映射文件中ParamaterType和ResultType中对象的编写。
<!--typeAliase标签使用方式--> <typeAliases> <typeAlias type="com.lcl.galaxy.mybatis.common.domain.UserDo" alias="user"/> </typeAliases>
那么UserMapper.xml文件中对于UserDao的路径引用就可以直接替换成user
<?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.lcl.galaxy.mybatis.xml.mapper.UserMapper"> <!-- 根据id获取用户信息 --> <select id="findUserById" parameterType="int" resultType="user"> select * from user where id = #{id} </select> </mapper>
对于mybatis,有一些默认的标签内容,例如int、double等。
3、mapper标签
mapper标签可以有四种写法,分别使用resource、url、class和package四种
(1)resource写法:这种写法就是前面demo和xml写法里面用到的
<mapper resource="mapper/demo/UserMapper.xml"></mapper>
(2)url写法:url内是xml文件的绝对路径,同时,这里不会区分nameSpace,只要Statement有重复,就会报错
<mapper url="file:///d://workSpace/selfWorkSpace/lcl-galaxy/lcl-galaxy-mybatis/src/main/resources/mapper/xml/UserMapper.xml"/>
(3)class写法:这种是对于注解Mapper写法的补充,在前面说注解写法时,必须新增了一行代码:sqlSessionFactory.getConfiguration().addMapper(UserAnnoMapper.class);因为没有地方可以加载Mapper文件;那么这种class方式,就解决了在SqlMapperConfig文件中加载注解Mapper的情况,因此,就可以在初始化SqlSessionFactory时不用addMapper
<mapper class="com.lcl.galaxy.mybatis.anno.mapper.UserAnnoMapper"/>
初始化时就不需要addMapper
@Before public void init() throws Exception { SqlSessionFactoryBuilder sessionFactoryBuilder = new SqlSessionFactoryBuilder(); InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); sqlSessionFactory = sessionFactoryBuilder.build(inputStream); //sqlSessionFactory.getConfiguration().addMapper(UserAnnoMapper.class); }
(4)package写法:该写法就是对class写法的补充,不需要详细到具体的Mapper类,直接加载package下的所有Mapper类
<package name="com.lcl.galaxy.mybatis.anno.mapper"/>
(三)输入映射和输出映射
1、输入类型:paramterType
输入类型paramterType可以分为简单类型、对象类型、Map类型和List集合类型
(1)传递简单类型:之前的代码示例就是传递简单类型,这里需要特殊说明的就是模糊查询
<!-- 根据名称模糊查询用户列表 --> <select id="findUserByUsername" parameterType="java.lang.String" resultType="user"> <!-- select * from user where username like '%${value}% --> select * from user where username like CONCAT('%',#{name},'%') </select>
这里不能直接写 select * from user where username like '%#{value}%,这是因为#{}会将String类型默认加上单引号,如果传参为lcl,那么实际的sql输出为 select * from user where username like '%'lcl'%,这样查询肯定是不行的,这里可以使用${}来查询,因为${}是直接将值替换的;但是有一个问题就是${}可能存在SQL注入的问题,因此我们不建议使用${},那么怎么处理呢,这里就可以使用sql中提供的CONCAT函数将多个字符串拼接起来就OK了。
说到这里,我们不妨说一下${}和#{}的去别点
#{} | ${} | |
区别一 | 相当于JDBC中的占位符?(PreparedStatement) | 相当于JDBC中的连接符+(Statement) |
区别二 |
进行输入映射的时候,会对输入内容进行解析(如果输入为String类型,则会自动加上单引号) |
进行输入映射的时候,不会进行内容解析,会将内容原样输出 |
区别三 | 如果进行简单类型的输入映射时,#{}里面名称可以任意 | 进行简单类型的输入映射时,${}中参数必须是value |
(2)传递对象类型
mapper中新增insert语句,这里要说一下,这里可以查询新增后的id,keyProperty指更新后的id赋给对象的哪个属性,order为执行顺序,order="AFTER"表示只在insert语句执行后获取相关信息,LAST_INSERT_ID()函数表示最新插入数据的id。
<insert id="insertUser" parameterType="user"> <selectKey keyProperty="id" order="AFTER" resultType="long"> select LAST_INSERT_ID() </selectKey> INSERT INTO user(username, birthday, sex, address) VALUES (#{username}, #{birthday}, #{sex}, #{address}); </insert>
Mapper类
public int insertUser(UserDo userDo);
测试类:这里说一句,insert语句返回的是insert条数,想要获取插入的id,则直接取在xml文件中keyProperty设置的属性,即userDo.getId()
@Test public void testInsertUser() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserDo userDo = UserDo.builder().username("qmm2").birthday(new Date()).sex("女").address("北京").build(); int row = userMapper.insertUser(userDo); sqlSession.commit(); log.info("row====[{}]==========insertId====[{}]", row, userDo.getId()); }finally { sqlSession.close(); } }
2、输出类型:paramterType
输出类型也分为简单类型、对象类型和Map类型,其中Map类型与对象类型基本一致。使用resultType时,要保证查询列与接收对象的属性保持一致。
(1)简单类型:使用简单类型接收时,必须只有一个返回值,以查询总条数为例:
xml文件
<select id="getCount" parameterType="String" resultType="int"> select count(1) from user where username = #{value }; </select>
Mapper对象
public int getCount(String username);
测试类
@Test public void testGetCount() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); int count = userMapper.getCount("qmm2"); log.info("count====[{}]", count); }finally { sqlSession.close(); } }
(2)对象类型,这个在前面的入门程序里面已经描述过了
3、映射配置:resultMap
如果查询列与接收对象的属性不一致,可以使用resultMap做一个映射,从而将查询结果映射到对象的属性上;其实ResultType的底层也是使用的ResultMap做的映射。
接下来可以使用别名查询来演示一下resultMap的使用
xml文件:使用resultMap做映射,其中id是后续其他查询要使用该映射map的标识,type为需要映射的对象;result子标签中property为对象属性,column为查询sql的查询列;在查询语句中,resultMap要指定使用resultMap的id。
<resultMap id="userAliaseMap" type="user"> <result property="username" column="uname"/> <result property="birthday" column="bir"/> <result property="sex" column="aaasex"/> <result property="address" column="addr"/> </resultMap> <select id="selectAsAliase" resultMap="userAliaseMap" parameterType="int"> select id, username uname, birthday bir, sex aaasex, address addr from user where id = #{id } </select>
Mapper对象
UserDo selectAsAliase(int id) throws Exception;
测试类
@Test public void testSelectAsAliase() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); try{ UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserDo user = userMapper.selectAsAliase(2); log.info("userDo====[{}]",user); }finally { sqlSession.close(); } }
三、高级应用
(一)关联查询
1、一对一查询
新增订单表及sql
CREATE TABLE `order_info` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `name` varchar(20) NOT NULL COMMENT '用户名', `orderId` bigint(20) DEFAULT NULL COMMENT '订单号', `payMoney` decimal(11,2) DEFAULT NULL COMMENT '订单实付金额', `orderCreateTime` datetime DEFAULT NULL COMMENT '订单下单时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; INSERT INTO `order_info`(`id`, `name`, `orderId`, `payMoney`, `orderCreateTime`) VALUES (2, 'lcl', 10001, 10.00, '2020-11-17 21:29:35'); INSERT INTO `order_info`(`id`, `name`, `orderId`, `payMoney`, `orderCreateTime`) VALUES (3, 'lcl', 10002, 12.00, '2020-11-18 21:29:53'); INSERT INTO `order_info`(`id`, `name`, `orderId`, `payMoney`, `orderCreateTime`) VALUES (4, 'lcl', 10003, 15.00, '2020-11-12 21:30:08'); INSERT INTO `order_info`(`id`, `name`, `orderId`, `payMoney`, `orderCreateTime`) VALUES (5, 'qmm', 10004, 11.00, '2020-11-24 21:30:23');
Do对象
package com.lcl.galaxy.mybatis.common.domain; import lombok.Data; import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.util.Date; @Data @NoArgsConstructor public class OrderInfoDo { private long id; private String username; private String orderId; private BigDecimal payMoney; private Date orderCreateTime; }
(1)resultType类型实现
resultType类型实现就比较简单,新增一个包含所有返回值的对象,然后使用关联查询查出所有需要的列就OK。
需要返回的Dto对象
package com.lcl.galaxy.mybatis.common.dto; import lombok.Data; import java.math.BigDecimal; import java.util.Date; @Data public class UserOrderDto2{ private long id; private String username; private Date birthday; private String sex; private String address; private String name; private String orderId; private BigDecimal payMoney; private Date orderCreateTime; }
Mapper类
List<UserOrderDto> selectUserOrderDtoByUserName(String name) throws Exception;
Xml文件
<select id="selectUserOrderDtoByUserName" resultType="com.lcl.galaxy.mybatis.common.dto.UserOrderDto2" parameterType="String"> SELECT order_info.*, user.username, user.address,user.birthday,user.sex FROM user LEFT JOIN order_info ON order_info.name = user.username where order_info.name = #{username} </select>
测试类
@Test public void testSelectUserOrderDtoByUserId() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); try{ UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<UserOrderDto> userOrderDto = userMapper.selectUserOrderDtoByUserName("qmm"); log.info("userOrderDto====[{}]",userOrderDto); }finally { sqlSession.close(); } }
(2)resultMap类型实现
resultType类型实现就比较简单,新增一个包含所有返回值的对象,然后使用关联查询查出所有需要的列就OK。
Dto
package com.lcl.galaxy.mybatis.common.dto; import com.lcl.galaxy.mybatis.common.domain.OrderInfoDo; import lombok.Data; import java.util.Date; @Data public class UserOrderDto extends OrderInfoDo{ private long id; private String username; private Date birthday; private String sex; private String address; private OrderInfoDo orderInfoDo; }
Mapper类
List<UserOrderDto> selectUserOrderDtoByUserName2(String name) throws Exception;
Xml文件
使用ResultMap方式时,需要用到association标签,该标签表示一对一关联映射,property属性表示关联查询的结果存于哪个对象中,javaType表示查询结果的映射类型
<resultMap id="userOrderDtoMap" type="com.lcl.galaxy.mybatis.common.dto.UserOrderDto"> <result column="us" property="username"/> <result column="address" property="address"/> <result column="birthday" property="birthday"/> <result column="sex" property="sex"/> <association property="orderInfoDo" javaType="com.lcl.galaxy.mybatis.common.domain.OrderInfoDo"> <result column="name" property="name"/> <result column="orderId" property="orderId"/> <result column="payMoney" property="payMoney"/> <result column="orderCreateTime" property="orderCreateTime"/> </association> </resultMap> <select id="selectUserOrderDtoByUserName2" resultMap="userOrderDtoMap" parameterType="String"> SELECT order_info.*, user.username as us, user.address as address,user.birthday as birthday,user.sex as sex FROM user LEFT JOIN order_info ON order_info.name = user.username where order_info.name = #{username} </select>
测试类
@Test public void testSelectUserOrderDtoByUserId2() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); try{ UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<UserOrderDto> userOrderDto = userMapper.selectUserOrderDtoByUserName2("qmm"); log.info("userOrderDto====[{}]",userOrderDto); }finally { sqlSession.close(); } }
2、一对多查询
一对多查询只能用resultMap
Dto
package com.lcl.galaxy.mybatis.common.dto; import com.lcl.galaxy.mybatis.common.domain.OrderInfoDo; import lombok.Data; import java.util.Date; import java.util.List; @Data public class UserOrdersDto { private long id; private String username; private Date birthday; private String sex; private String address; private List<OrderInfoDo> orderInfoDoList; }
Mapper类
List<UserOrdersDto> selectUserOrdersDtoByUserName(String name) throws Exception;
Xml文件
与一对一唯一的差别就是将映射标签association改成collection,即一对一改为集合;在collection标签中,property属性是对象中的属性值,ofType为集合属性中对象的值
<resultMap id="userOrdersDtoMap" type="com.lcl.galaxy.mybatis.common.dto.UserOrdersDto"> <result column="us" property="username"/> <result column="address" property="address"/> <result column="birthday" property="birthday"/> <result column="sex" property="sex"/> <collection property="orderInfoDoList" ofType="com.lcl.galaxy.mybatis.common.domain.OrderInfoDo"> <result column="name" property="name"/> <result column="orderId" property="orderId"/> <result column="payMoney" property="payMoney"/> <result column="orderCreateTime" property="orderCreateTime"/> </collection> </resultMap> <select id="selectUserOrdersDtoByUserName" resultMap="userOrdersDtoMap" parameterType="String"> SELECT order_info.*, user.username as us, user.address as address,user.birthday as birthday,user.sex as sex FROM user LEFT JOIN order_info ON order_info.name = user.username where order_info.name = #{username} </select>
测试类
@Test public void testSelectUserOrderDtosByUserId() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); try{ UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<UserOrdersDto> userOrdersDtoList = userMapper.selectUserOrdersDtoByUserName("lcl"); log.info("userOrdersDtoList====[{}]",userOrdersDtoList); }finally { sqlSession.close(); } }
(二)延迟加载
延迟加载即懒加载,是指在关联查询时,延迟关联对象的查询;(1)Mybatis的延迟加载只对关联对象有效,对于主加载对象都是直接查询;(2)Mybatis的延迟加载需要通过ResultMap的collection和association标签才可以验证(3)延迟加载可以减少数据库的压力;但是也会有N+1的问题。(4)延迟加载可以分为直接加载、侵入式延迟和深度延迟。
直接加载:执行完主查询select后马上执行关联对象的select查询
侵入式延迟:查询主对象时,不会马上查询关联对象,但是如果使用主对象的某一个属性的时候,就会查询关联对象
深度延迟:查询主对象时,不会马上查询关联对象,使用主对象的某一个属性的时候,也不会查询关联对象,但是当使用关联对象的属性时,才会查询关联对象
设置延迟加载使用setting标签,然后lazyLoadingEnabled属性表示是否开启懒加载,true:开启,false:不开启,默认false;aggressiveLazyLoading属性表示懒加载模式,ture为侵入式开关 false为深度延迟加载,默认false
(1)直接加载配置:
<settings> <setting name="lazyLoadingEnabled" value="false"/> </settings>
(2)侵入式懒加载配置
<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>
(3)深度懒加载
<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>
之前说到,懒加载可能会引发N+1问题,即会查询N+1次,1次是指的查询主信息,N指的是关联数据有多少条就会查询多少次,因此如果关联数据多的情况,使用懒加载反而会增加数据库压力
(三)缓存
Mybatis缓存中如果存在数据,则不再查询数据库,用于减小数据库压力。Mybatis缓存分为一级缓存和二级缓存。
1、一级缓存
一级缓存是Session级别的缓存,在操作数据库时创建了一个SqlSession对象,在该对象中有一个HashMap对象用于存储缓存,因此不同的Sqlsession之间的缓存数据区域是互不影响的。
例如使用id查询用户信息时,会先从HashMap查看是否存在缓存,如果存在,则直接返回,不存在则查询数据库。如果出现增删改操作,则会清除缓存。下面就使用一个例子说明,第一次查询会发送sql,第二次使用相同的id查询则不会发送sql,但是如果使用一次插入操作后,下一次查询则会重新发送sql。
@Test public void testMybatisCache() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); try{ UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserDo user = userMapper.findUserById(2); log.info("user====[{}]",user); UserDo user2 = userMapper.findUserById(2); log.info("user2====[{}]",user2); UserDo user3 = new UserDo(); user3.setUsername("test"); userMapper.insertUser(user2); UserDo user4 = userMapper.findUserById(2); log.info("user4====[{}]",user4); }finally { sqlSession.close(); } }
2、二级缓存
二级缓存可以跨Session。
例如使用不同的SqlSession查询相同的id用户,如果开启了二级缓存,则会使用二级缓存;同样的,如果在同一个NameSpace中操作了增删改操作,则会清除缓存
二级缓存开启总开关
<settings> <!-- 开启二级缓存总开关 --> <setting name="cacheEnabled" value="true"/> </settings>
然后还有更细粒度的控制,如果哪个NameSpace需要使用二级缓存,需要在其Xml文件中开启(在Xml中加入cache标签)
<!-- 开启二级缓存 --> <cache/>
然后就是验证
@Test public void testMybatisCache2() throws Exception { SqlSession sqlSession1 = sqlSessionFactory.openSession(); UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class); SqlSession sqlSession2 = sqlSessionFactory.openSession(); UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class); SqlSession sqlSession3 = sqlSessionFactory.openSession(); UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class); SqlSession sqlSession4 = sqlSessionFactory.openSession(); UserMapper userMapper4 = sqlSession4.getMapper(UserMapper.class); UserDo user1 = userMapper1.findUserById(2); log.info("=========================================================="); log.info("user1====[{}]",user1); sqlSession1.close(); UserDo user2 = userMapper2.findUserById(2); log.info("=========================================================="); log.info("user2====[{}]",user2); sqlSession2.close(); UserDo user3 = new UserDo(); user3.setUsername("test2"); userMapper3.insertUser(user3); sqlSession3.commit(); sqlSession3.close(); log.info("=========================================================="); log.info("user3====[{}]",user3); UserDo user4 = userMapper4.findUserById(2); log.info("=========================================================="); log.info("user4====[{}]",user4); sqlSession4.close(); }
同样的,还可以有更细粒度的控制:如果在XML中配置了cache标签,那么如果在该NameSpace中有方法不想使用二级缓存,那么可以在select标签中使用useCache属性来设置
<!-- 根据id获取用户信息 --> <select id="findUserByIdNoCache" parameterType="int" resultType="user" useCache="false"> select * from user where id = #{id} </select>
除了上述的useCache属性外,Mybatis还提供了flushCache属性,用来控制该操作是否会刷新二级缓存,select标签默认为false,增删改标签默认为true
<!-- 根据id获取用户信息 --> <select id="findUserByIdNoCache" parameterType="int" resultType="user" useCache="false" flushCache="false"> select * from user where id = #{id} </select>
二级缓存的使用场景:查询效率要求比较高,但是数据实时性不是很高的情况,可以使用二级缓存
二级缓存使用注意事项:在使用二级缓存的时候,要设置一下刷新缓存的间隔,(cache标签中的flushInterval属性)
二级缓存的使用局限:缓存的清理是整个NameSpace级别的,因此不能控制到一条数据的维度,因此还需要借助其他的缓存来处理,例如Redis。其实这里也可使将查询单独写一个Xml文件,这样就可以避免增删改操作清除缓存。
(四)动态SQL
动态Sql简单的说就是用来做sql拼接的,常见的标签有sql、where、if、foreach等
if标签是用来判断,foreach标签是用来循环
package com.lcl.galaxy.mybatis.common.vo; import lombok.*; import java.util.List; @Builder @Data @NoArgsConstructor @AllArgsConstructor public class QueryVo { private String sex; private List<String> addressList; }
<select id="findUserList" parameterType="com.lcl.galaxy.mybatis.common.vo.QueryVo" resultType="user"> select * from user where 1=1 <if test="sex != null and sex != ''"> and sex = #{sex} </if> <foreach collection="addressList" item="addr" open=" and address in (" separator="," close=")" index=""> #{addr} </foreach> </select>
where标签主要是可以省略1=1的条件,而sql标签则是一个标签块,可以直接印用
<sql id="base_column_list"> id,username,birthday,sex,address </sql> <select id="findUserList2" parameterType="com.lcl.galaxy.mybatis.common.vo.QueryVo" resultType="user"> select <include refid="base_column_list"/> from user <where> <if test="sex != null and sex != ''"> and sex = #{sex} </if> <foreach collection="addressList" item="addr" open=" and address in (" separator="," close=")" index=""> #{addr} </foreach> </where> </select>
如果要引用其他NameSpace中的sql块,则需要加上NameSpace
<include refid="namespace.base_column_list"/>
(五)Mybatis逆向工程
https://www.cnblogs.com/liconglong/p/11692412.html
(六)PageHelper分页插件
https://www.cnblogs.com/liconglong/p/11693782.html
-----------------------------------------------------------
---------------------------------------------
朦胧的夜 留笔~~