@Mapper
@Mapper 注解通常用于Java项目中,特别是在使用MyBatis或类似的ORM(对象关系映射)框架时。这个注解的主要目的是简化Mapper接口的实现过程,使得开发者不需要手动编写实现类,框架会自动处理SQL语句的执行和结果集的映射。
使用场景
在MyBatis中,@Mapper注解可以应用于接口上,标记这个接口为一个Mapper接口,即这个接口中定义的方法将会与数据库中的表进行交互。MyBatis会扫描这些带有@Mapper注解的接口,并动态地生成实现类,实现类中包含了执行SQL语句和映射结果集到Java对象的逻辑。
优点
简化开发:开发者只需要关注SQL语句的编写和接口方法的定义,无需关心SQL的执行和结果集的映射。
提高可维护性:SQL语句和Java代码分离,便于管理和维护。
灵活性:支持动态SQL,可以根据不同的条件构建不同的SQL语句。
示例
假设有一个用户表user,我们想要通过MyBatis来操作这个表,可以定义一个Mapper接口如下:
@Mapper
public interface UserMapper {
// 根据ID查询用户
User selectUserById(Integer id);
// 插入用户
int insertUser(User user);
// 更新用户
int updateUser(User user);
// 删除用户
int deleteUser(Integer id);
}
在这个例子中,UserMapper接口被@Mapper注解标记,MyBatis会识别这个接口,并为其生成实现类。然后,你就可以在Service层或其他地方注入UserMapper接口,直接调用其方法来操作数据库了。
注意事项
- 确保MyBatis的配置文件(如mybatis-config.xml)中配置了Mapper接口的位置,以便MyBatis能够扫描到这些接口。
- 在Spring Boot项目中,通常不需要在每个Mapper接口上都加上@Mapper注解,而是可以在启动类上使用@MapperScan注解来指定Mapper接口所在的包路径,这样Spring Boot会自动扫描并注册这些Mapper接口。
- SQL语句通常写在XML文件中,或者通过注解的方式直接写在Mapper接口的方法上。MyBatis会根据这些信息来执行SQL语句并映射结果集。
注解的方式
@Select: 用于标注在 Mapper 接口的方法上,指定 SQL 查询语句。
@Insert: 用于标注在 Mapper 接口的方法上,指定 SQL 插入语句。
@Update: 用于标注在 Mapper 接口的方法上,指定 SQL 更新语句。
@Delete: 用于标注在 Mapper 接口的方法上,指定 SQL 删除语句。
@Param: 用于方法的参数上,当 Mapper 接口方法有多个参数时,可以使用 @Param 注解来命名参数,以便在 SQL 语句中引用。
@Results: 用于映射 SQL 查询结果到 Java 对象。它可以包含多个 @Result 注解,用于指定列名和 Java 对象属性的映射关系。
@Result: 用于 @Results 注解内部,指定列名和 Java 对象属性的映射关系。
@One: 用于一对一关联映射。
@Many: 用于一对多关联映射。
@Select
- 查询操作(Select)
单个对象查询
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User selectUserById(@Param("id") Integer id);
}
列表查询
import org.apache.ibatis.annotations.Select;
public interface UserMapper {
@Select("SELECT * FROM user WHERE age > #{minAge}")
List<User> selectUsersByAge(@Param("minAge") int minAge);
}
@Insert
- 插入操作(Insert)
import org.apache.ibatis.annotations.Insert;
public interface UserMapper {
@Insert("INSERT INTO user(name, age) VALUES(#{name}, #{age})")
int insertUser(User user);
}
@Update
- 更新操作(Update)
import org.apache.ibatis.annotations.Update;
public interface UserMapper {
@Update("UPDATE user SET name = #{name}, age = #{age} WHERE id = #{id}")
int updateUser(User user);
}
@Delete
- 删除操作(Delete)
import org.apache.ibatis.annotations.Delete;
public interface UserMapper {
@Delete("DELETE FROM user WHERE id = #{id}")
int deleteUser(@Param("id") Integer id);
}
@Param
@Param 是 MyBatis 中一个非常有用的注解,它用于命名 Mapper 接口方法的参数。当 Mapper 接口方法中的参数不止一个,或者需要在 XML 映射文件中引用方法参数时,@Param 注解就显得尤为重要。
在 MyBatis 中,如果 Mapper 接口方法的参数只有一个,那么 MyBatis 可以默认使用这个参数来填充 SQL 语句中的占位符(比如 #{})。但是,如果方法有多个参数,或者虽然只有一个参数但是需要在 SQL 语句中引用它的属性名(比如,传递了一个对象而不是基本数据类型),那么就需要使用 @Param 注解来明确指定参数名。
使用场景
- 多个参数的情况:当 Mapper 接口方法接受多个参数时,可以使用 @Param 注解为每个参数命名,以便在 SQL 语句中通过指定的名称来引用它们。
- 对象参数的属性访问:虽然通常对象参数的属性可以通过点符号(.)直接访问(假设开启了 useColumnLabel 为 true 或使用 MyBatis 3.4.0 及以上版本并且没有复杂的嵌套结果映射),但在某些情况下,明确指定属性名可以使 SQL 语句更加清晰。
- 与 XML 映射文件结合使用:在 XML 映射文件中,当需要引用 Mapper 接口方法的参数时,可以使用 @Param 注解提供的名称。
示例
多个参数
public interface UserMapper {
// 使用 @Param 注解为参数命名
User selectUserByIdAndName(@Param("id") Integer id, @Param("name") String name);
}
<!-- 对应的 XML 映射文件中,可以通过 #{id} 和 #{name} 来引用参数 -->
<select id="selectUserByIdAndName" resultType="User">
SELECT * FROM user WHERE id = #{id} AND name = #{name}
</select>
对象参数的属性访问(虽然不常用,但可以展示如何使用)
虽然通常不需要为对象参数的属性使用 @Param(因为可以直接通过点符号访问),但假设有特殊需求:
// 实际上,对于对象参数的属性,通常不需要使用 @Param
// 这里只是为了展示如果硬要使用的话(通常不推荐)
public interface UserMapper {
// 假设这里为了某种特殊需求,我们还是想通过 @Param 指定属性名(但通常不这么做)
// 注意:实际上这并不会改变 SQL 语句中如何引用对象属性的方式
User selectUserBySomeCriteria(@Param("user") User user);
}
<!-- 但 XML 映射文件中仍然通过点符号访问对象的属性 -->
<select id="selectUserBySomeCriteria" resultType="User">
SELECT * FROM user WHERE name = #{user.name} AND age = #{user.age}
</select>
然而,需要注意的是,上面的 User selectUserBySomeCriteria(@Param(“user”) User user); 示例中,@Param(“user”) 实际上并没有改变 SQL 语句中引用对象属性的方式。它主要的作用是,如果这个方法在 XML 映射文件中被 、、 或 标签的某个属性(如 parameterMap,尽管在 MyBatis 3 中已经不常用了)中引用时,提供了一个明确的参数名。但在大多数情况下,对于对象参数的属性访问,我们直接使用点符号即可。
@Results和@Result
使用@Results和@Result进行复杂结果映射
当查询结果需要映射到对象的复杂属性(如关联对象或集合)时,可以使用@Results和@Result注解。
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
public interface UserMapper {
@Select("SELECT u.*, a.address AS address FROM user u LEFT JOIN address a ON u.id = a.user_id WHERE u.id = #{id}")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "name", column = "name"),
@Result(property = "address", column = "address", javaType = String.class)
})
User selectUserWithAddressById(@Param("id") Integer id);
}
注意:上面的selectUserWithAddressById方法是一个简化的例子,实际上如果address是一个复杂的对象而不是简单的字符串,你可能需要使用@One或@Many注解来进行关联映射。
@One
@One 是 MyBatis 在处理关联对象时的一个注解,它用于一对一(One-to-One)关系映射。当你想在查询一个对象时,同时关联查询出另一个与之对应的一个对象时,可以使用 @One 注解。
@One 注解通常与 标签的功能相对应,但它是用于注解方式的 MyBatis 映射。它告诉 MyBatis 在执行查询时,如何将查询结果中的一个嵌套结果集映射到 Java 对象的关联属性上。
使用场景
假设有两个表:user 和 user_detail,它们之间是一对一的关系(例如,每个用户都有一个与之对应的用户详情记录)。在查询用户信息时,你可能希望同时获取到该用户的详情信息。
示例
首先,定义 Java 对象模型:
public class User {
private Integer id;
private String name;
private UserDetail userDetail; // 一对一关联的对象
// 省略getter和setter方法
}
public class UserDetail {
private Integer id;
private Integer userId; // 外键,关联user表的id
private String address;
// 省略getter和setter方法
}
然后,在 Mapper 接口中使用 @One 注解来映射关联对象:
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
public interface UserMapper {
@Select("SELECT u.*, ud.address FROM user u LEFT JOIN user_detail ud ON u.id = ud.user_id WHERE u.id = #{id}")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "name", column = "name"),
@Result(property = "userDetail", javaType = UserDetail.class, one = @One(select = "selectUserDetailById"))
})
User selectUserWithDetailById(Integer id);
// 这是一个嵌套查询,用于获取用户详情
@Select("SELECT id, user_id, address FROM user_detail WHERE user_id = #{userId}")
UserDetail selectUserDetailById(Integer userId);
}
-
在上面的例子中,@One 注解被用于 userDetail 属性的映射上,它指定了一个嵌套查询 selectUserDetailById 来获取用户详情。当执行 selectUserWithDetailById 方法时,MyBatis 会首先执行主查询来获取用户信息,然后对于每个用户,它会执行 selectUserDetailById 方法来获取对应的用户详情,并将结果设置到 User 对象的 userDetail 属性中。
-
注意:虽然 @One 注解提供了便利的关联映射方式,但在处理大量数据时,嵌套查询可能会导致性能问题,因为对于主查询中的每一行,都会执行一次嵌套查询。在这种情况下,考虑使用 标签在 XML 映射文件中进行映射,并可能采用连接查询(JOIN)来优化性能。
@Many
@Many 注解在 MyBatis 的官方注解中并不存在。相反,MyBatis 提供了 @One 和 @Many 的功能对应注解,但 @Many 的功能是通过 @Results 注解中的 @Result 标记为 many 类型的 映射来实现的。在注解方式中,这通常是通过在 @Result 注解内嵌套 @Many 的替代注解(实际上是通过 many 属性或 @Many 类似的自定义注解,但后者不是 MyBatis 原生提供的)来实现的,但更常见的是直接通过 many 属性指定一个查询方法来映射一对多(Many-to-One 或 Many-to-Many)的关系。
不过,为了说明如何在使用注解时映射一对多关系,我们可以看一个使用 many 属性的例子,而不是 @Many 注解(因为它不存在)。
示例
假设我们有两个实体类:User 和 Post,其中 User 可以有多个 Post。
public class User {
private Integer id;
private String name;
private List<Post> posts; // 一对多关联
// 省略getter和setter方法
}
public class Post {
private Integer id;
private Integer userId; // 外键
private String title;
private String content;
// 省略getter和setter方法
}
在 Mapper 接口中,我们可以使用 @Results 和 @Result(带有 many 属性)来映射这种一对多关系:
import org.apache.ibatis.annotations.Many;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
// 注意:@Many 不是一个有效的 MyBatis 注解,这里仅用于说明
// 实际上,我们会使用 many 属性在 @Result 注解中
public interface UserMapper {
@Select("SELECT u.*, p.id as 'post.id', p.title as 'post.title', p.content as 'post.content' FROM user u LEFT JOIN post p ON u.id = p.user_id WHERE u.id = #{id}")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "name", column = "name"),
@Result(property = "posts", javaType = List.class, column = "id",
many = @Many(select = "selectPostsByUserId")) // 注意:这里使用的是 many 属性,而不是 @Many 注解
})
User selectUserWithPostsById(Integer id);
@Select("SELECT id, user_id, title, content FROM post WHERE user_id = #{userId}")
List<Post> selectPostsByUserId(Integer userId);
}
但是,上面的代码中有一点需要注意:MyBatis 原生注解中并没有 @Many。实际上,我们应该在 @Result 注解中使用 many 属性,并指定一个方法来执行嵌套查询,以获取关联的多个对象。然而,由于 MyBatis 的注解支持可能不如 XML 映射文件那么灵活,因此在处理复杂关系时,XML 映射文件通常是更好的选择。
在上面的代码中,我已经将 many = @Many(select = “selectPostsByUserId”) 修改为了一个假设的语法,以说明意图。实际上,你应该这样写:
@Result(property = "posts", javaType = List.class, column = "id",
many = @org.apache.ibatis.annotations.ResultMap(value = "postResultMap"))
然后,在 Mapper XML 文件中定义一个 resultMap 来映射 Post 列表:
<resultMap id="postResultMap" type="Post">
<id column="post_id" property="id"/>
<result column="title" property="title"/>
<result column="content" property="content"/>
</resultMap>
但是,如果你坚持要使用注解并且不想在 XML 文件中定义 resultMap,你可能需要寻找或创建一个自定义的注解处理器来模拟 @Many 的行为,因为 MyBatis 原生并不支持这样的注解。然而,这通常是不必要的,因为 MyBatis 提供的注解和 XML 映射功能已经足够强大,可以满足大多数需求。
动态SQL
虽然注解方式不如XML方式在动态SQL方面灵活,但MyBatis还是提供了一些基本的动态SQL支持,如@SelectProvider、@InsertProvider等,这些注解允许你编写一个类来动态构建SQL语句。然而,在实际项目中,对于复杂的动态SQL,通常推荐使用XML映射文件。
在MyBatis中,动态字段通常指的是在构建SQL查询时,根据某些条件动态地包含或排除表中的某些字段(列),或者根据条件动态地构建WHERE子句、ORDER BY子句等。MyBatis提供了强大的动态SQL功能,允许你根据不同的条件来构建不同的SQL语句。
动态SQL主要通过MyBatis的XML Mapper文件中的<if>、<choose>(<when>、<otherwise>)、<where>、<set>、<trim>、<foreach>
等标签来实现。
以下是一个基于动态字段的MyBatis示例,展示了如何根据条件动态地选择查询的字段:
Mapper 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.itheima.mapper.ProductsMapper">
<select id="selectProducts" resultType="com.itheima.pojo.Products">
SELECT
<trim suffixOverrides=",">
<if test="prodIdField">
prod_id,
</if>
<if test="prodNameField">
prod_name,
</if>
<if test="vendIdField">
vend_id,
</if>
<if test="prodPriceField">
prod_price,
</if>
<if test="prodDescField">
prod_desc,
</if>
</trim>
FROM products
<where>
<trim prefixOverrides="AND">
<if test="prodName != null">
AND prod_name = #{prodName}
</if>
</trim>
</where>
</select>
</mapper>
在这个例子中,selectProducts方法允许你根据传入的参数(prodNameOption、prodIdOption、prodName等)来动态地选择查询的字段,并且还可以根据prodName参数来过滤结果。
Mapper 接口
// UserMapper.java
@Mapper
public interface ProductsMapper {
List<Products> selectProducts(Boolean prodNameOption,
Boolean prodIdOption,
String prodName);
使用Mapper
在你的服务层中,你可以这样调用selectProducts方法:
@Service
public class ProductsService {
@Autowired
private ProductsMapper productsMapper;
public List<Products> selectProducts(Boolean prodNameOption,
Boolean prodIdOption,
String prodName) {
//调用mapper查询数据
return productsMapper.selectProducts(prodNameOption,prodIdOption,prodName);
}
在你的控制层中,你可以这样调用selectProducts方法:
@Slf4j
@RequestMapping("/products")
@RestController
public class ProductsController {
@Autowired
private ProductsService productsService;
@GetMapping
public Result selectProducts(@RequestParam(value = "prodNameOption", defaultValue = "true") Boolean prodNameOption,
@RequestParam(value = "prodIdOption", defaultValue = "false") Boolean prodIdOption,
String prodName){
// public Result selectProducts( Boolean prodNameOption,
// Boolean prodIdOption,
// String prodName){
log.info("selectProducts数据{},{},{}",prodNameOption,prodIdOption,prodName);
//调用service查询数据
List<Products> productsList = productsService.selectProducts(prodNameOption,prodIdOption,prodName);
return Result.success(productsList);
}
}
// 处理查询结果
在这个例子中,我们设置了prodNameOption和prodIdOption为true,表示我们希望查询结果中包含prod_name和prod_id字段,同时,我们还设置了一个prodName参数来过滤结果。
然而,需要注意的是,当使用动态字段时,你应该确保你的应用逻辑能够正确处理可能返回的不完整数据(即某些字段可能为null或未包含)。此外,如果数据库表结构发生变化,你也需要相应地更新你的Mapper XML文件和相关的Java代码。
结论
注解方式在简单场景下非常方便快捷,但对于复杂的SQL语句和映射关系,XML映射文件提供了更多的灵活性和控制力。因此,在选择使用注解还是XML时,应根据项目的具体需求和团队的偏好来决定。