MyBatis
- 动力节点MyBatis课程笔记
- 自用。如若侵权,请联系我,会尽快删除。
一、 MyBatis 概述
-
MyBatis是Java中的一种数据持久层框架
-
本质上就是对JDBC的封装,通过MyBatis完成CRUD
-
MyBatis属于半自动化ORM框架
-
ORM:对象关系映射
-
- O(Object):Java虚拟机中的Java对象
- R(Relational):关系型数据库
- M(Mapping):将Java虚拟机中的Java对象映射到数据库表中一行记录,或是将数据库表中一行记录映射成Java虚拟机中的一个Java对象
二、第一个MyBatis程序
- MyBatis 中主要有两个配置文件
- mybatis-config.xml,这是核心配置文件,主要配置连接数据库的信息
- XxxMapper.xml,这个文件是专门用来编写sql语句的配置文件,一般是一个表对应一个
mybatis-config.xml
<?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> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/ych"/> <property name="username" value="root"/> <property name="password" value="toor"/> </dataSource> </environment> </environments> <mappers> <!--sql映射文件创建好之后(XxxMapper.xml),需要将该文件路径配置到这里--> <mapper resource="CarMapper.xml"/> <!-- url 属性 从绝对路径中加载程序,不建议使用--> <!-- <mapper url="绝对路径"> --> </mappers> </configuration>
CarMapper.xml
<?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"> <!--namespace先随意写一个--> <mapper namespace="car"> <!--insert sql:保存一个汽车信息--> <insert id="insertCar"> insert into t_car (id,car_num,brand,guide_price,produce_time,car_type) values (null,'1003','丰田mirai',40.30,'2014-10-05','氢能源') </insert> </mapper>
MyBatisIntroductionTest
package com.ych.mybatis.test; 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 java.io.IOException; import java.io.InputStream; public class MyBatisIntroductionTest { public static void main(String[] args) throws IOException { // 获取SqlSessionFactoryBuilder对象 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); // 获取SqlsessionFactory 对象 InputStream resourceAsStream = Resources.getResourceAsStream("mybaits-config.xml"); // 一般情况下是 一个数据库对应一个sqlsessionfactory对象 SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream); // 获取SqlSession 对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 执行sql语句 int insertCar = sqlSession.insert("insertCar"); System.out.println("插入了"+insertCar+"条数据"); // 手动提交 sqlSession.commit(); sqlSession.close(); }
-
第一个程序中的小细节
- mybatis中sql语句的结尾";"可以省略
- Resource.getResourceAsStream
- 小技巧:凡是遇到resource,大部分情况下,这种加载资源的方式就是从类的根路径下开始加载的
- 优点:项目的移植性很强
-
关于mybatis的事务管理机制
- 在mybatis-config.xml文件中,可以通过以下配置进行mybatis的事务管理,
<transactionManager type="type"/>
- type属性的值包括两个
- JDBC和MANAGED
- 在mybatis中提供两种事务管理机制
- JDBC事务管理器
- mybatis框架自己管理事务,自己采用原生JDBC代码区管理事务
- MANAGED事务管理机制
- mybatis不再负责事务的管理,而是交给其它容器区负责,例如:Spring
- JDBC事务管理器
- 🌟当autoCommit是true时,就表示没有开启事务
- 在mybatis-config.xml文件中,可以通过以下配置进行mybatis的事务管理,
-
Junit 单元测试
// 测试用例 // 名字规范:你要测试的类名+Test public class CarMapperTest{ // 一般是一个业务方法对应于一个测试方法 // 测试方法的规范,以test开始, public void testXxxx(){} // 测试方法 @Test // @Test注解非常重要,被这个注解标注的方法就是一个单元测试方法 public void testInsert(){ // 单元测试中两个非常重要的值 // 一个是 实际值(被测试的业务方法的真正执行结果) // 一个是 期望值(被执行的业务方法的期望结果) // 获取实际值和期望值 int actual = XXXX.insert(); int expected = xxx; // 加断言进行测试 Assert.assertEquals(excepted,actual); } @Test public void testUpdate(){} }
- Mybatis 继承日志组件
- 引入logback的依赖
- 引入logback所必须得xml配置文件
- 文件名是logback.xml或者logback-test.xml,不能是其它名字
- 必须放在根目录下
三、CRUD
1. 增加
- 在mybatis中使用
#{}
作为占位符
insert into t_car (id,car_num,brand,guide_price,produce_time,car_type) values (null,#{k1},#{k2},#{k3},#{k4},#{k5}) // 变量命名最好是见名知意,这里命名并不规范
- 使用map对sql语句进行传值
Map<String,Object> map = new HashMap<>(); map.put("k1","1011"); map.put("k2","吉利"); map.put("k3",11.98); map.put("k4","2022-10-11"); map.put("k5","混沌"); int insertCar = sqlSession.insert("insertCar", map);
- 使用POJO类给sql语句传值
mybatis-config.xml
- {}中的car类中的属性名
<insert id="insertCar"> insert into t_car (id,car_num,brand,guide_price,produce_time,car_type) values (null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType}) </insert>
CarMapperTest
@Test public void testInsertCarByPojo(){ SqlSession sqlSession = SqlSessionUtil.openSession(); // 封装数据 Car car = new Car(null, "3333", "海豹06", 9.98, "2024-03-11", "混合动力"); int insertCar = sqlSession.insert("insertCar", car); System.out.println(insertCar); // 执行sql sqlSession.commit(); sqlSession.close(); }
2. 删除
mybatis-config.xml
<delete id="deleteCar"> # 一个变量就可以随便起名字 delete from t_car where id = #{id} </delete>
CarMapperTest
@Test public void testDeleteCar(){ SqlSession sqlSession = SqlSessionUtil.openSession(); int deleteCar = sqlSession.delete("deleteCar", 9); System.out.println(deleteCar); sqlSession.commit(); sqlSession.close(); }
3. 修改
mybatis-config.xml
<update id="updateCar"> update t_car set guide_price = #{guidePrice} where id = #{id}; </update>
CarMapperTest
@Test public void testUpdateCar(){ SqlSession sqlSession = SqlSessionUtil.openSession(); // 封装数据 Car car = new Car(1L, "", "", 9.98, "", ""); int insertCar = sqlSession.update("updateCar", car); System.out.println(insertCar); // 执行sql sqlSession.commit(); sqlSession.close(); }
4. 查询
- 查询单个
mybatis-config.xml
<!-- resultType 设置查询内容要转换的类型--> <select id="selectById" resultType="com.ych.mybatis.pojo.Car"> <!-- as 是起别名,为了使数据库和car类中的变量名保持一致--> select id, car_num as carNum ,brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where id = #{id} </select>
CarMapperTest
@Test public void testSelectById(){ SqlSession sqlSession = SqlSessionUtil.openSession(); Object car = sqlSession.selectOne("selectById", 1L); System.out.println(car); sqlSession.close(); }
- 查所有
mybatis-config.xml
<select id="selectAll" resultType="com.ych.mybatis.pojo.Car"> select id, car_num as carNum ,brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car </select>
CarMapperTest
@Test public void testSelectAll(){ SqlSession sqlSession = SqlSessionUtil.openSession(); List<Object> cars = sqlSession.selectList("selectAll"); System.out.println(cars); sqlSession.close(); }
4. namespace 命名空间
- 在sql XxxMapper.xml 文件当中有一个namespace,这个属性用于指定命名空间,用来防止id重复
- 在java程序中,mybatis的sqlId完整的写法是:
namespace.Id
四、MyBatis核心配置文件详解
1. environments
- 一个SqlSessionFactory对象对应一个数据库表
- 一个SqlSessionFactory对应一个环境environment
mybatis-config.xml
<?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> <!--默认使用开发环境--> <!--<environments default="powernode">--> <!--默认使用生产环境--> <environments default="powernode"> <!--开发环境--> <environment id="powernode"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/powernode"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> <!--生产环境--> <environment id="production"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <mapper resource="CarMapper.xml"/> </mappers> </configuration>
ConfigurationTest.testEnvironment
// 使用默认数据库 SqlSessionFactory sqlSessionFactorysqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml")); SqlSession sqlSession = sqlSessionFactory.openSession(true); int count = sqlSession.insert("insertCar", car); System.out.println("插入了几条记录:" + count); // 使用指定数据库 SqlSessionFactory sqlSessionFactory1 = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "powernode"); //指定数据库为“powernode” SqlSession sqlSession1 = sqlSessionFactory1.openSession(true); int count1 = sqlSession1.insert("insertCar", car); System.out.println("插入了几条记录:" + count1);
2. transactionManager
<transactionManager type="[JDBC|MANAGED]"/>
- 当事务管理器是:JDBC
- 采用JDBC的原生事务机制:
- 开启事务:conn.setAutoCommit(false);
- 处理业务…
- 提交事务:conn.commit();
- 采用JDBC的原生事务机制:
- 当事务管理器是:MANAGED
- 交给JavaEE容器去管理事务,但目前使用的是本地程序,没有容器的支持,当mybatis找不到容器的支持时:没有事务。也就是说只要执行一条DML语句,则提交一次。
3. dataSource
- 作用:为程序提供Connection对象
<dataSource type="[UNOOLED|POOLED|JNDI]">
- UNPOOLED:不使用数据库连接池技术,每一个请求过来之后,都是创建新的Connection对象
- POOLED:使用mybatis自己实现的数据库连接池
- JNDI:集成其它第三方的数据库连接池
-
其余重要属性:
- poolMaximumActiveConnections:最大的活动的连接数量。默认值10
- poolMaximumIdleConnections:最大的空闲连接数量。默认值5
- poolMaximumCheckoutTime:强行回归池的时间。默认值20秒。
- poolTimeToWait:当无法获取到空闲连接时,每隔20秒打印一次日志,避免因代码配置有误,导致傻等。(时长是可以配置的)
<!--最大连接数--> <property name="poolMaximumActiveConnections" value="3"/> <!--这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。--> <property name="poolTimeToWait" value="20000"/> <!--强行回归池的时间--> <property name="poolMaximumCheckoutTime" value="20000"/> <!--最多空闲数量--> <property name="poolMaximumIdleConnections" value="1"/>
4. properties
- 连接数据库的信息可以单独写到一个属性资源文件
.properties
中
jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/powernode
mybatis-config4.xml
<?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="jdbc.properties"> <property name="jdbc.username" value="root"/> <property name="jdbc.password" value="root"/> </properties> <environments default="dev"> <environment id="dev"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!--${key}使用--> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="CarMapper.xml"/> </mappers> </configuration>
5. mapper
- mapper标签用来指定SQL映射文件的路径
- 不同的sql文件,可以有多个mapper标签
五、手写MyBatis框架
- 先不学
六、在WEB中应用MyBatis(使用MVC架构模式)
七、使用javassist生成类(了解)
八、MyBatis中接口代理机制及使用
- 使用mybatis获取dao接口代理类对象
AccountDao accountDao = (AccountDao)sqlSession.getMapper(AccountDao.class);
- 使用以上代码的前提是:AccountMapper.xml文件中的namespace必须和dao接口的全限定名称一致,id必须和dao接口中方法名一致
<?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.ych.bank.dao.AccountDao"> <!--与dao接口的全限定名称一致--> <select id="selectByActno" resultType="com.ych.bank.pojo.Account"> <!--与dao接口中方法名一致--> select * from t_act where actno = #{actno} </select> <update id="updateByActno"> <!--与dao接口中方法名一致--> update t_act set balance = #{balance} where actno = #{actno} </update> </mapper>
accountServiceImpl
package com.ych.bank.service.impl; import com.ych.bank.com.ych.bank.exceptions.MoneyNotEnoughtException; import com.ych.bank.com.ych.bank.exceptions.TransferException; import com.ych.bank.dao.AccountDao; import com.ych.bank.pojo.Account; import com.ych.bank.service.AccountService; import com.ych.bank.utils.SqlSessionUtil; public class AccountServiceImpl implements AccountService { // 获取dao对象,使用getMapper方法 private AccountDao accountDao = SqlSessionUtil.openSession().getMapper(AccountDao.class); // private AccountDao accountDao = new AccountDaoImpl(); @Override public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughtException, TransferException { // 1. 判断转出账户余额是否充足(select) Account fromAct = accountDao.selectByActno(fromActno); // 2. 如果转出账户余额不足,提示用户 if (fromAct.getBalance() < money){ throw new MoneyNotEnoughtException("余额不足"); } // 3. 如果转出账户余额充足,更新转出账号余额(update) //先更新内存中java对象的account余额 Account toAct = accountDao.selectByActno(toActno); fromAct.setBalance(fromAct.getBalance()-money); toAct.setBalance(toAct.getBalance()+money); // 4. 更新转入账户余额(update) int count = accountDao.updateByActno(fromAct); count += accountDao.updateByActno(toAct); if (count != 2) { throw new TransferException("转账异常"); } } }
九、MyBatis小技巧
9.1 #{}和${}\
- #{}:先编译sql语句,再给占位符传值,底层是PreparedStatement实现。可以防止sql注入,比较常用。
- ${}:先进行sql语句拼接,然后再编译sql语句,底层是Statement实现。存在sql注入现象。只有在需要进行sql语句关键字拼接的情况下才会用到
- 如果需要SQL语句的关键字放到SQL语句中,只能使用
${}
,因为#{}
是以值的形式放到SQL语句中的
- 需求:根据produceTime,按升序返回查询结果
<select id="selectAllByProduceTime" resultType="com.ych.mybatis.pojo.Car"> select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car order by produce_time ${AscOrDeasc} </select>
- 模糊查询
- 需求:查询奔驰系列的汽车。【只要品牌brand中含有奔驰两个字的都查询出来。
- 使用${}
<select id="selectLikeByBrand" resultType="Car"> select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType from t_car where brand like '%${brand}%' </select>
- 使用#{}
<select id="selectLikeByBrand" resultType="Car"> select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType from t_car where brand like concat('%',#{brand},'%') </select>
<select id="selectLikeByBrand" resultType="Car"> select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType from t_car where brand like "%"#{brand}"%" </select>
9.2 typeAliases(MyBatis别名机制)
- 首先要注意typeAliases标签的放置位置,如果不清楚的话,可以看看错误提示信息。
- typeAliases标签中的typeAlias可以写多个。
- typeAlias:
- type属性:指定给哪个类起别名
- alias属性:别名。
- alias属性不是必须的,如果缺省的话,type属性指定的类型名的简类名作为别名。
- alias是大小写不敏感的。也就是说假设alias=“Car”,再用的时候,可以CAR,也可以car,也可以Car,都行。
- 第一种:自定义别名
mybatis-config.xml
<typeAliases> <typeAlias type="com.powernode.mybatis.pojo.Car" alias="Car"/> </typeAliases>
- 第二种:默认机制
<typeAliases> <!-- 用类名作为别名,等价于 alias = "Car"--> <typeAlias type="com.powernode.mybatis.pojo.Car"/> </typeAliases>
第三种:package
mybatis用提供package的配置方式,只需要指定包名,该包下的所有类都自动起别名,别名就是简类名。并且别名不区分大小写。
mybatis-config.xml
<typeAliases> <package name="com.powernode.mybatis.pojo"/> </typeAliases>
- 该包下所有的类都将用简类名作为别名
9.3 插入数据时,获取自动生成的主键
CarMapper.xml
<insert id="insertUseGeneratedKeys" useGeneratedKeys="true" keyProperty="id"> insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType}) </insert>
useGeneratedKeys="true"
:获得自动生成的主键keyProperty="id"
:将获取的主键绑定到类的id
属性上
十、MyBatis参数处理
1. 单个简单类型参数
- 简单类型包括:
- byte short int long float double char
- Byte Short Integer Long Float Double Character
- String
- java.util.Date
- java.sql.Dat
- SQL映射文件中的配置比较完整的写法是
<select id="selectByName" resultType="student" parameterType="java.lang.String">
select * from t_student where name = #{name, javaType=String, jdbcType=VARCHAR}
</select>
- 其中sql语句中的javaType,jdbcType,以及select标签中的parameterType属性,都是用来帮助mybatis进行类型确定的。不过这些配置多数是可以省略的。因为mybatis它有强大的自动类型推断机制。
- javaType:可以省略
- jdbcType:可以省略
- parameterType:可以省略
2. Map参数
3. 实体类参数
4. 多参数
StudentMapper.xml
<select id="selectByNameAndSex" resultType="student"> <!--select * from t_student where name = #{name} and sex = #{sex}--> select * from t_student where name = #{arg0} and sex = #{arg1} </select>
- 多个参数时,mybatis会默认将参数依次命名为arg0,arg1…或者param0,param1…,顺序与接口中方法的参数顺序一致
5. @Param注解
- 多个参数时,可以是
@Param
参数自定义参数名
StudentMapper.xml
<select id="selectByNameAndSex" resultType="student"> select * from t_student where name = #{name} and sex = #{sex} </select>
StudentMapper接口
/** * 根据name和age查询 * @param name * @param age * @return */ List<Student> selectByNameAndAge(@Param(value="name") String name, @Param("age") int age);
十一、MyBatis查询语句专题
- resultMap结果映射
- 查询结果的列名和java对象的属性名对应不上怎么办?
- 第一种方式:as 给列起别名
- 第二种方式:使用resultMap进行结果映射
- 第三种方式:是否开启驼峰命名自动映射(配置settings)
1. 使用resultMap进行结果映射
CarMapper.xml
<?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.ych.mybatis.mapper.CarMapper"> <!-- 1. 专门定义一个结果映射,在这个结果映射当中指定数据库表的字段名和java类的属性名的对应关系 2. type属性:用来指定pojo类的类名 3. id属性:指定resultMap的唯一标识,这个id将来要作为select标签中使用 --> <resultMap id="carResultMap" type="com.ych.mybatis.pojo.Car"> <!--指定主键--> <id property="id" column="id"></id> <!--property后面填写pojo属性类的属性名,column后面填写数据库表的字段名--> <!--如果属性名和数据库中的字段本身就是一致的,则可以省略,但id最好不要省略--> <result property="carNum" column="car_num"/> <result property="brand" column="brand"/> <result property="guidePrice" column="guide_price"/> <result property="produceTime" column="produce_time"/> <result property="carType" column="car_type"/> </resultMap> <!--select 标签的resultMap属性,用来指定使用哪个结果映射,resultMap后面的值是resultMap的id --> <select id="selectAllByRetMap" resultMap="carResultMap"> select * from t_car </select> </mapper>
CarMapperTest
@Test public void testSelectAllByRetMap(){ SqlSession sqlSession = SqlSessionUtil.openSession(); CarMapper mapper = sqlSession.getMapper(CarMapper.class); List<Car> cars = mapper.selectAllByRetMap(); cars.forEach(car -> System.out.println(car)); sqlSession.close(); }
2. 开启驼峰命名自动映射
- 使用这种方式的前提是:属性名遵循Java的命名规范,数据库表的列名遵循SQL的命名规范。
- Java命名规范:首字母小写,后面每个单词首字母大写,遵循驼峰命名方式。
- SQL命名规范:全部小写,单词之间采用下划线分割
- 注意单词别拼错:
mapUnderscoreToCamelCase
实体类中的属性名 | 数据库表的列名 |
---|---|
carNum | car_num |
carType | car_type |
produceTime | produce_time |
mybatis-config.xml
<!--放在properties标签后面--> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
CarMapper.xml
<select id="selectAllByMapUnderscoreToCamelCase" resultType="Car"> select * from t_car </select>
CarMapperTest.testSelectAllByMapUnderscoreToCamelCase
@Test public void testSelectAllByMapUnderscoreToCamelCase(){ CarMapper carMapper = SqlSessionUtil.openSession().getMapper(CarMapper.class); List<Car> cars = carMapper.selectAllByMapUnderscoreToCamelCase(); System.out.println(cars); }
十二、动态SQL
1. if标签
CarMapper.xml
<select id="selectByMultiCondition" resultType="com.ych.mybatis.pojo.Car"> select * from t_car where 1=1 <!--if标签动态拼接sql语句--> <if test="brand != null and brand != ''"> and brand like "%"#{brand}"%" </if> <if test="guide_price !=null and guide_price != ''"> and guide_price > #{guide_price} </if> <if test="car_type != null and car_type != ''"> and car_type = #{car_type} </if> </select>
2. where标签
-
where标签的作用:让where子句更加动态智能。
-
所有条件都为空时,where标签保证不会生成where子句。
-
自动去除某些条件前面多余的and或or。
-
CarMapper.xml
<?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.ych.mybatis.mapper.CarMapper"> <select id="selectByMultiConditionWithWhere" resultType="com.ych.mybatis.pojo.Car"> select * from t_car <!-- 1. 所有条件都为空时,where标签保证不会生成where子句。 2. 自动去除某些条件**前面**多余的and或or。--> <where> <if test="brand != null and brand != ''"> and brand like "%"#{brand}"%" </if> <if test="guide_price !=null and guide_price != ''"> and guide_price > #{guide_price} </if> <if test="car_type != null and car_type != ''"> and car_type = #{car_type} </if> </where> </select> </mapper>
3. trim标签
-
trim标签的属性:
-
prefix:在trim标签中的语句前添加内容
-
suffix:在trim标签中的语句后添加内容
-
prefixOverrides:前缀覆盖掉(去掉)
-
suffixOverrides:后缀覆盖掉(去掉)
-
<select id="selectByMultiConditionWithTrim" resultType="car">
select * from t_car
<trim prefix="where" suffixOverrides="and|or">
<if test="brand != null and brand != ''">
brand like #{brand}"%" and
</if>
<if test="guidePrice != null and guidePrice != ''">
guide_price >= #{guidePrice} and
</if>
<if test="carType != null and carType != ''">
car_type = #{carType}
</if>
</trim>
</select>
5. set标签
- 主要使用在update语句当中,用来生成set关键字,同时去掉最后多余的“,”
- 比如我们只更新提交的不为空的字段,如果提交的数据是空或者"",那么这个字段我们将不更新。
<update id="updateWithSet">
update t_car
<set>
<if test="carNum != null and carNum != ''">
car_num = #{carNum},
</if>
<if test="brand != null and brand != ''">
brand = #{brand},
</if>
<if test="guidePrice != null and guidePrice != ''">
guide_price = #{guidePrice},
</if>
<if test="produceTime != null and produceTime != ''">
produce_time = #{produceTime},
</if>
<if test="carType != null and carType != ''">
car_type = #{carType},
</if>
</set>
where id = #{id}
</update>
4. choose when otherwise
-
只有一个分支会被选择
-
这三个标签是在一起使用的:
<choose> <when></when> <when></when> <when></when> <otherwise></otherwise> </choose>
- 等价于
if(){ }else if(){ }else if(){ }else if(){ }else{ }
<select id="selectWithChoose" resultType="car"> select * from t_car <where> <choose> <when test="brand != null and brand != ''"> brand like #{brand}"%" </when> <when test="guidePrice != null and guidePrice != ''"> guide_price >= #{guidePrice} </when> <otherwise> produce_time >= #{produceTime} </otherwise> </choose> </where> </select>
6. foreach标签
- 用来执行批量删除或者批量插入
- 批量删除
- 第一种方式
delete from t_car where id in(1,2,3);
CarMapper.xml
<!-- collection:集合或数组 item:集合或数组中的元素 separator:分隔符 open:foreach标签中所有内容的开始 close:foreach标签中所有内容的结束 --> <delete id="deleteBatchByForeach"> delete from t_car where id in <foreach collection="ids" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete>
- 拼接的SQL
Preparing: delete from t_car where id in ( ? , ? , ? ) Parameters: 1(Long), 2(Long), 3(Long)
- 第二种方式
delete from t_car where id = 1 or id = 2 or id = 3;
CarMapper.xml
<delete id="deleteBatchByForeach2"> delete from t_car where <foreach collection="ids" item="id" separator="or"> id = #{id} </foreach> </delete>
- 拼接的SQL
Preparing: delete from t_car where id = ? or id = ? or id = ? Parameters: 1(Long), 2(Long), 3(Long)
- 批量插入
insert into t_car values (null,'1001','凯美瑞',35.0,'2010-10-11','燃油车'), (null,'1002','比亚迪唐',31.0,'2020-11-11','新能源'), (null,'1003','比亚迪宋',32.0,'2020-10-11','新能源')
CarMapper.xml
<insert id="insertBatchByForeach"> insert into t_car values <foreach collection="cars" item="car" separator=","> (null,#{car.carNum},#{car.brand},#{car.guidePrice},#{car.produceTime},#{car.carType}) </foreach> </insert>
- 拼接的SQL
Preparing: insert into t_car values (null,?,?,?,?,?) , (null,?,?,?,?,?) , (null,?,?,?,?,?) Parameters: 1001(String), 凯美瑞2021(String), 32.0(Double), 2021-10-11(String), 燃油车(String), 1002(String), 凯美瑞2022(String), 33.0(Double), 2022-10-11(String), 燃油车(String), 1003(String), 凯美瑞2023(String), 35.0(Double), 2023-10-11(String), 燃油车(String)
7. sql标签与include标签
- sql标签用来声明sql片段
- include标签用来将声明的sql片段包含到某个sql语句当中
- 作用:代码复用。易维护
<sql id="carCols">
id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType
</sql>
<select id="selectAllRetMap" resultType="map">
select <include refid="carCols"/> from t_car
</select>
<select id="selectAllRetListMap" resultType="map">
select <include refid="carCols"/> carType from t_car
</select>
<select id="selectByIdRetMap" resultType="map">
select <include refid="carCols"/> from t_car where id = #{id}
</select>
十三、MyBatis的高级映射及延迟加载
1. 多对一
- 第一种方式:级联属性映射
StudentMapper.xml
<?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.powernode.mybatis.mapper.StudentMapper"> <resultMap id="studentResultMap" type="Student"> <id property="sid" column="sid"/> <result property="sname" column="sname"/> <result property="clazz.cid" column="cid"/> <result property="clazz.cname" column="cname"/> </resultMap> <select id="selectById" resultMap="StudentResultMap"> select s.sid,s.sname,c.cid,c.cname from t_stu s left join t_clazz c on s.cid = c.cid where s.sid = #{sid} </select> </mapper>
- 第二种方式:associate
StudentMapper.xml
<?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.powernode.mybatis.mapper.StudentMapper"> <resultMap id="StudentResultMap" type="Student"> <id property="sid" column="sid"/> <result property="sname" column="sname"/> <!--用association来关联另一张表--> <association property="clazz" javaType="Clazz"> <id property="cid" column="cid"/> <result property="cname" column="cname"/> </association> </resultMap> <select id="selectById" resultMap="StudentResultMap"> select s.sid,s.sname,c.cid,c.cname from t_stu s left join t_clazz c on s.cid = c.cid where s.sid = #{sid} </select> </mapper>
- 以上两种方式的sql语句都是:
Preparing: select s.sid,s.sname,c.cid,c.cname from t_stu s left join t_clazz c on s.cid = c.cid where s.sid = ? Parameters: 1(Integer)
- 第三种方式:分布查询
- 其他位置不需要修改,只需要修改以及添加以下三处
- 第一处:association中select位置填写sqlId。sqlId=namespace+id。其中column属性作为这条子sql语句的条件。
StudentMapper.xml
<resultMap id="studentResultMap" type="Student"> <id property="sid" column="sid"/> <result property="sname" column="sname"/> <association property="clazz" select="com.powernode.mybatis.mapper.ClazzMapper.selectByCid" column="cid"/> </resultMap> <select id="selectBySid" resultMap="studentResultMap"> select s.* from t_student s where sid = #{sid} </select>
- 第二处:在ClazzMapper接口中添加方法
ClazzMapper接口
package com.powernode.mybatis.mapper; import com.powernode.mybatis.pojo.Clazz; public interface ClazzMapper { Clazz selectByCid(Integer cid); }
- 第三处:在ClazzMapper.xml文件中进行配置
ClazzMapper.xml
<?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.powernode.mybatis.mapper.ClazzMapper"> <select id="selectByCid" resultType="Clazz"> select * from t_clazz where cid = #{cid} </select> </mapper>
- sql执行过程
Preparing: select * from t_stu where sid = ? Parameters: 1(Integer)
Preparing: select * from t_clazz where cid = ? Parameters: 1001(Integer)
- 分步优点:
- 代码复用性增强。
- 支持延迟加载。【暂时访问不到的数据可以先不查询。提高程序的执行效率。
2. 多对一延迟加载
- 在mybatis-config.xml中打开全局延迟加载
<settings> <!--开启驼峰自动映射--> <setting name="mapUnderscoreToCamelCase" value="true"/> <!--开启全局延迟加载--> <setting name="lazyLoadingEnabled" value="true"/> </settings>
- 如果某一步不需要延迟加载,可以在XxxMapper.xml的association标签张宏关闭
<!-- 分布查询 --> <resultMap id="StudentResultMap" type="Student"> <id property="sid" column="sid"/> <result property="sname" column="sname"/> <association property="clazz" select="com.ych.mybatis.mapper.ClazzMapper.selectByCid" column="cid" fetchType="eager"/> <!--关闭延迟加载--> </resultMap>
3. 一对多映射
- 第一种方式:collection
ClazzMapper接口
package com.powernode.mybatis.mapper; import com.powernode.mybatis.pojo.Clazz; public interface ClazzMapper { Clazz selectByCid(Integer cid); Clazz selectClazzAndStusByCid(Integer cid); }
ClazzMapper.xml
<resultMap id="clazzResultMap" type="Clazz"> <id property="cid" column="cid"/> <result property="cname" column="cname"/> <collection property="stus" ofType="Student"> <!--指定集合stus中元素的类型--> <id property="sid" column="sid"/> <result property="sname" column="sname"/> </collection> </resultMap> <select id="selectClazzAndStusByCid" resultMap="clazzResultMap"> select * from t_clazz c join t_student s on c.cid = s.cid where c.cid = #{cid} </select>
ClazzMapperTest.testSelectClazzAndStusByCid
package com.powernode.mybatis.test; import com.powernode.mybatis.mapper.ClazzMapper; import com.powernode.mybatis.pojo.Clazz; import com.powernode.mybatis.utils.SqlSessionUtil; import org.junit.Test; public class ClazzMapperTest { @Test public void testSelectClazzAndStusByCid() { ClazzMapper mapper = SqlSessionUtil.openSession().getMapper(ClazzMapper.class); Clazz clazz = mapper.selectClazzAndStusByCid(1001); System.out.println(clazz); } }
- 第二种方式
- 修改以下三个位置即可
ClazzMapper.xml
<resultMap id="clazzResultMap" type="Clazz"> <id property="cid" column="cid"/> <result property="cname" column="cname"/> <!--主要看这里--> <collection property="stus" select="com.powernode.mybatis.mapper.StudentMapper.selectByCid" column="cid"/> </resultMap> <!--sql语句也变化了--> <select id="selectClazzAndStusByCid" resultMap="clazzResultMap"> select * from t_clazz c where c.cid = #{cid} </select>
StudentMapper接口
/** * 根据班级编号获取所有的学生。 * @param cid * @return */ List<Student> selectByCid(Integer cid);
StudentMapper.xml
<select id="selectByCid" resultType="Student"> select * from t_student where cid = #{cid} </select>
4. 一对多的延迟加载
- 和多对一的延迟加载机制是一样的
十四、MyBatis 的缓存
- 缓存的作用:通过减少IO的方式,来提高程序的执行效率。
- mybatis的缓存:将select语句的查询结果放到缓存(内存)当中,下一次还是这条select语句的话,直接从缓存中取,不再查数据库。一方面是减少了IO。另一方面不再执行繁琐的查找算法。效率大大提升。
- mybatis缓存包括:
- 一级缓存:将查询到的数据存储到SqlSession中。
- 二级缓存:将查询到的数据存储到SqlSessionFactory中。
- 或者集成其它第三方的缓存:比如EhCache【Java语言开发的】、Memcache【C语言开发的】等。
- 缓存只针对于DQL语句,也就是说缓存机制只对应select语句
1. 一级缓存
-
一级缓存默认是开启的。不需要做任何配置。
-
原理:只要使用同一个SqlSession对象执行同一条SQL语句,就会走缓存
-
什么情况下不走缓存?
- 第一种:不同的SqlSession对象。
- 第二种:查询条件变化了。
-
一级缓存失效情况包括两种:
- 第一种:第一次查询和第二次查询之间,手动清空了一级缓存。
sqlSession.clearCache();
- 第二种:第一次查询和第二次查询之间,执行了增删改操作。【这个增删改和哪张表没有关系,只要有insert delete update操作,一级缓存就失效】
- 第一种:第一次查询和第二次查询之间,手动清空了一级缓存。
2. 二级缓存
-
二级缓存的范围是SqlSessionFactory。
-
使用二级缓存需要具备以下几个条件:
<setting name="cacheEnabled" value="true">
全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认就是true,无需设置。在MyBatis-config.xml文件中- 在需要使用二级缓存的SqlMapper.xml文件中添加配置:
<cache />
- 使用二级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接口
- SqlSession对象关闭或提交之后,一级缓存中的数据才会被写入到二级缓存当中。此时二级缓存才可用。
-
二级缓存的失效:只要两次查询之间出现了增删改操作。二级缓存就会失效。【一级缓存也会失效】
-
二级缓存的相关配置:
- viction:指定从缓存中移除某个对象的淘汰算法。默认采用LRU策略。
-
- LRU:Least Recently Used。最近最少使用。优先淘汰在间隔时间内使用频率最低的对象。(其实还有一种淘汰算法LFU,最不常用。)
-
- FIFO:First In First Out。一种先进先出的数据缓存器。先进入二级缓存的对象最先被淘汰。
-
- SOFT:软引用。淘汰软引用指向的对象。具体算法和JVM的垃圾回收算法有关。
-
- WEAK:弱引用。淘汰弱引用指向的对象。具体算法和JVM的垃圾回收算法有关。
- flushInterval:
-
- 二级缓存的刷新时间间隔。单位毫秒。如果没有设置。就代表不刷新缓存,只要内存足够大,一直会向二级缓存中缓存数据。除非执行了增删改。
- readOnly:
-
- true:多条相同的sql语句执行之后返回的对象是共享的同一个。性能好。但是多线程并发可能会存在安全问题。
-
- false:多条相同的sql语句执行之后返回的对象是副本,调用了clone方法。性能一般。但安全。
- size:
-
- 设置二级缓存中最多可存储的java对象数量。默认值1024
3. MyBatis集成EhCache
- 集成EhCache是为了代替mybatis自带的二级缓存。一级缓存是无法替代的。
- mybatis对外提供了接口,也可以集成第三方的缓存组件。比如EhCache、Memcache等。都可以。
- EhCache是Java写的。Memcache是C语言写的。所以mybatis集成EhCache较为常见。
十五、MyBatis的逆向工程
- 所谓的逆向工程是:根据数据库表逆向生成Java的pojo类,SqlMapper.xml文件,以及Mapper接口类等。
- 逆向工程配置与生成
第一步:基础环境准备
- 新建模块:mybatis-011-generator
- 打包方式:jar
第二步,再pom中添加逆向工程模块
pom.xml
<!--定制构建过程--> <build> <!--可配置多个插件--> <plugins> <!--其中的一个插件:mybatis逆向工程插件--> <plugin> <!--插件的GAV坐标--> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.4.1</version> <!--允许覆盖--> <configuration> <overwrite>true</overwrite> </configuration> <!--插件的依赖--> <dependencies> <!--mysql驱动依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> </dependencies> </plugin> </plugins> </build>
- 第三步:配置generatorConfig.xml
- 该文件名必须叫做:generatorConfig.xml
- 该文件必须放在类的根路径下。
generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!-- targetRuntime有两个值: MyBatis3Simple:生成的是基础版,只有基本的增删改查。 MyBatis3:生成的是增强版,除了基本的增删改查之外还有复杂的增删改查。 --> <context id="DB2Tables" targetRuntime="MyBatis3"> <!--防止生成重复代码--> <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/> <commentGenerator> <!--是否去掉生成日期--> <property name="suppressDate" value="true"/> <!--是否去除注释--> <property name="suppressAllComments" value="true"/> </commentGenerator> <!--连接数据库信息--> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/XXX" userId="root" password="root"> </jdbcConnection> <!-- 生成pojo包名和位置 --> <javaModelGenerator targetPackage="com.XXX.mybatis.pojo" targetProject="src/main/java"> <!--是否开启子包--> <property name="enableSubPackages" value="true"/> <!--是否去除字段名的前后空白--> <property name="trimStrings" value="true"/> </javaModelGenerator> <!-- 生成SQL映射文件的包名和位置 --> <sqlMapGenerator targetPackage="com.XXX.mybatis.mapper" targetProject="src/main/resources"> <!--是否开启子包--> <property name="enableSubPackages" value="true"/> </sqlMapGenerator> <!-- 生成Mapper接口的包名和位置 --> <javaClientGenerator type="xmlMapper" targetPackage="com.XXX.mybatis.mapper" targetProject="src/main/java"> <property name="enableSubPackages" value="true"/> </javaClientGenerator> <!-- 表名和对应的实体类名--> <table tableName="XXX" domainObjectName="XXX"/> </context> </generatorConfiguration>
- 第四步:运行插件
- QBC 风格的多条件查询
- QBC 风格:Query By Criteria 一种查询方式,比较面向对象,看不到sql语句。
CarExample carExample = new CarExample(); carExample.createCriteria() .andBrandEqualTo("丰田霸道") .andGuidePriceGreaterThan(new BigDecimal(60.0)); carExample.or().andProduceTimeBetween("2000-10-11", "2022-10-11"); mapper.selectByExample(carExample); sqlSession.close();