Mybatis11-基于注解的开发
MyBatis 基于注解的开发是一种简化 MyBatis 映射配置的方式它不使用 XML 文件来编写sql语句而是通过在 Java 接口方法上添加注解来实现 SQL 映射。适用于 SQL 简单、变化不大的情况。【注意】若是sql语句本身很复杂不建议用注解的形式还是推荐用xml文件编写sql语句或者可以两种方式混合使用。一、常用注解列表用于 Mapper 接口方法注解说明Select执行查询语句返回单条或多条记录Insert执行插入语句Update执行更新语句Delete执行删除语句Results自定义结果映射字段名和属性名不一致时使用Result单个字段映射规则配合Results使用One一对一关联查询Many一对多关联查询SelectProvider动态 SQL使用 Java 方法生成 SQLInsertProvider动态插入UpdateProvider动态更新DeleteProvider动态删除二、示例基础使用1、实体类 Car2、接口UserMapperpublic interface UserMapper { Delete(delete from t_car where id #{id}) int deleteByPrimaryKey(Long id); Insert(insert into t_car (id, car_num, brand, guide_price, produce_time, car_type) values(null, #{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType})) int insert(Car car); Select(select * from t_car id #{id}) Car selectByPrimaryKey(Long id); Select(select * from t_car) ListCar selectAll(); Update(update t_car set car_num #{carNum}, brand #{brand} where id #{id}) int updateByPrimaryKey(Car car); }三、字段名和属性名不一致时3-1、解决方式一mybatis配置文件中添加setting开启驼峰配置settings !-- 开启驼峰映射 -- setting namemapUnderscoreToCamelCase valuetrue/ /settings3-2、解决方式二使用Results注解四、Spring Boot MyBatis配置步骤Mapper 接口加上Mapper注解使用MapperScan(com.example.mapper)扫描 Mapper 包不需要再写Mapper.xml五、适合场景和建议场景建议SQL 简单单表增删改查✅ 推荐使用注解SQL 复杂、动态拼接多❌ 建议使用 XML更灵活可维护大型项目、多人协作❌ 推荐使用 XML易读可控六、One注解的用法One是 MyBatis 注解方式中处理一对一或严格的多对一关联查询的核心注解。它对应 XML 映射中的association标签。1、它解决什么问题假设你有两张表-- 用户表 CREATE TABLE user ( id INT PRIMARY KEY, username VARCHAR(50), dept_id INT -- 所属部门ID ); -- 部门表 CREATE TABLE department ( id INT PRIMARY KEY, dept_name VARCHAR(50) );查询用户时你想同时把部门对象也查出来User里包含一个Department对象。这就是一对一/多对一关联。XML 方式用association注解方式用One。2、基本语法One不能单独使用必须配合Result的one属性public interface UserMapper { Select(SELECT id, username, dept_id FROM user WHERE id #{id}) Results({ Result(id true, column id, property id), Result(column username, property username), // 关键在这里dept_id 字段映射到 user 对象里的 department 属性 Result( column dept_id, // 当前SQL查出来的列作为参数传给子查询 property department, // User实体类里的属性名 one One( select com.example.mapper.DeptMapper.selectById, // 子查询方法全路径 fetchType FetchType.LAZY // 加载方式LAZY延迟 / EAGER立即 ) ) }) User selectUserWithDept(Integer id); }对应的DeptMapperpublic interface DeptMapper { Select(SELECT id, dept_name as deptName FROM department WHERE id #{id}) Department selectById(Integer id); }3、核心参数拆解参数说明column主查询中作为关联条件的字段通常是外键ID。子查询会把它作为参数传入。propertyJava 实体类中被映射的属性名必须是对象类型如Department。select子查询的全限定方法名Mapper接口全路径.方法名。fetchTypeLAZY延迟加载用到时才查或EAGER立即加载默认。4、延迟加载 vs 立即加载Result( column dept_id, property department, one One( select com.example.mapper.DeptMapper.selectById, fetchType FetchType.LAZY // 延迟加载 ) )EAGER默认查User时自动连带把Department也查出来发两条SQL。LAZY查User时不发第二条SQL只有当你代码里调用user.getDepartment()时才去查。注意LAZY需要配置开启且只在 SqlSession 生命周期内有效。如果你在 Service 层返回给 Controller 后再懒加载会报LazyInitializationException session 已关闭。5、和Many的区别注解对应XML场景实体属性类型Oneassociation一对一 / 多对一单个对象DepartmentManycollection一对多集合ListOrder示例对比// 一个用户属于一个部门 - One Result(column dept_id, property department, one One(select ...)) // 一个用户有多个订单 - Many Result(column id, property orders, many Many(select ...))6、常见踩坑点1.column写错必须是主查询 SQL 返回的列名不是 Java 属性名。如果主查询没查dept_id子查询就收不到参数。2. 子查询方法参数名不匹配子查询方法参数必须能接收column传过来的值。通常是单个基本类型Select(... WHERE id #{id}) // 这里的参数名无所谓MyBatis只传一个值 Department selectById(Integer id);3. 循环依赖N1问题如果查100个用户每个用户都触发一次One查询就会发101条SQL1条主查询100条子查询。3-1、N1 问题是什么场景你要查100个用户每个用户关联一个部门。用One嵌套查询你刚才学的方式Select(SELECT id, username, dept_id FROM user) Results({ Result(column dept_id, property department, one One(select com.example.mapper.DeptMapper.selectById)) }) ListUser selectAllUsers();实际执行的SQLSELECT id, username, dept_id FROM user—— 查出100条用户然后MyBatis循环100次每条都执行SELECT * FROM department WHERE id ??分别是dept_id_1, dept_id_2...总共发了 101 条SQL 1条主查询 100条子查询。这就是N1查N条主数据触发N1次数据库交互。3-2、解决方案一条SQL JOIN ResultMap手动映射核心思想不通过One发多条SQL而是一条SQL把两张表的数据一起查回来然后用ResultMap告诉MyBatis哪列映射到主对象哪列映射到关联对象。1. 写一条 JOIN SQLpublic interface UserMapper { // 一条SQL把user和department的数据都查出来 Select( SELECT u.id as user_id, u.username, d.id as dept_id, d.dept_name FROM user u LEFT JOIN department d ON u.dept_id d.id WHERE u.id #{id} ) // 使用 ResultMap 指定映射规则下面定义 ResultMap(userWithDeptResultMap) User selectUserWithDeptByJoin(Integer id); }2. 定义ResultMap手动映射Results(id userWithDeptResultMap, value { // 映射 User 主对象的字段 Result(id true, column user_id, property id), Result(column username, property username), // 映射 Department 关联对象 // 这里不用 One而是直接映射到 user.department 下的属性 Result(column dept_id, property department.id), Result(column dept_name, property department.deptName) }) Select(...) // 这个注解可以放在任意一个方法上作为模板 void dummyMethod(); // 或者放在接口上不同版本支持不同关键点property department.deptName表示把dept_name这列的值塞到user对象里的department对象的deptName属性上。MyBatis 会自动帮你创建department对象并赋值。3-3、两种方式对比维度One嵌套查询JOIN ResultMapSQL条数N1 条性能差1 条性能好SQL复杂度简单单表查询稍复杂要JOIN适用场景关联数据偶尔用且数据量小关联数据每次必用或数据量大延迟加载支持 LAZY不支持一次性全查代码量少简洁稍多要手动写列映射【小结】如果关联数据是高频使用的不要用One嵌套查询因为它会产生N1 性能问题。写一条 JOIN SQL配合ResultMap手动映射把关联对象的字段直接映射到property 对象.属性上这样只用一条SQL就能完成关联查询避免多次数据库交互。4. 字段映射别名如果子查询的列名和实体属性名不一致如dept_namevsdeptName记得在子查询 SQL 里加as别名或配合Results映射。7、总结MyBatis 的One注解用于处理一对一关联映射它对应 XML 中的association。使用时需要指定column关联字段、property实体属性和select子查询方法。它支持LAZY延迟加载但要注意懒加载的Session 边界问题。和Many一对多的区别在于One映射的是单个对象而Many映射的是集合。