代码实现特殊的字段的基本功能
在使用 tk.mybatis.mapper.common.Mapper
提供的通用 Mapper 接口时,如果你需要添加自定义的 SQL 查询方法,并且希望通过 XML 文件来定义这些方法,你不需要在 Mapper
接口中为这些自定义方法提供方法体,而是通过在 XML 文件中定义这些方法,并确保 Mapper
接口的命名空间和方法名与 XML 中的配置一致。
示例
假设你有一个 User
实体类,并且你想添加一个自定义方法来根据用户的邮箱查询用户。
实体类
public class User {private Integer id;private String name;private String email;// Getters and Setters
}
Mapper 接口
在 UserMapper
接口中,你只需要声明方法名,而不需要提供实现。方法名应该与 XML 文件中定义的 id
一致。
import tk.mybatis.mapper.common.Mapper;public interface UserMapper extends Mapper<User> {// 自定义方法声明,方法名与 XML 中的 id 对应User selectUserByEmail(String email);
}
UserMapper.xml
创建一个 UserMapper.xml
文件,并在其中定义自定义查询方法。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.mapper.UserMapper"><!-- 自定义查询方法 --><select id="selectUserByEmail" parameterType="string" resultType="com.example.entity.User">SELECT * FROM users WHERE email = #{email}</select></mapper>
说明
-
命名空间
:
- 在 XML 文件中,
<mapper>
标签的namespace
属性应该与UserMapper
接口的全限定名一致。例如,如果UserMapper
接口在com.example.mapper
包中,那么namespace
应该是com.example.mapper.UserMapper
。
- 在 XML 文件中,
-
方法名与
id
一致:
UserMapper
接口中的方法名selectUserByEmail
应该与 XML 文件中<select>
标签的id
属性一致。
-
参数类型和返回类型
:
- 在 XML 文件中,
parameterType
指定了传入参数的类型,resultType
指定了返回结果的类型。确保这些类型与你的实体类和参数类型匹配。
- 在 XML 文件中,
使用
在你的服务层或控制器中,你可以通过注入 UserMapper
来使用这些方法:
@Autowired
private UserMapper userMapper;public User getUserByEmail(String email) {return userMapper.selectUserByEmail(email);
}
通过这种方式,你可以在继承通用 Mapper 接口的同时,使用 XML 文件来定义自定义的 SQL 查询方法。
在 MyBatis 中,XML 映射文件的路径需要与 MyBatis 的配置保持一致,以便 MyBatis 能够正确加载和解析这些文件。通常,XML 映射文件的放置路径和命名规则需要遵循以下几点建议:
1. 放置路径
- 资源目录:通常,XML 映射文件会放置在
src/main/resources
目录下。这个目录是 Maven 项目默认的资源目录,MyBatis 会从这里加载资源文件。 - 包结构:为了保持项目的组织性和可维护性,建议将 XML 文件放置在与对应 Mapper 接口相同的包结构下。例如,如果
UserMapper
接口在com.example.mapper
包中,那么UserMapper.xml
文件可以放置在src/main/resources/com/example/mapper
目录下。
2. 配置 MyBatis
-
mybatis-config.xml
:在 MyBatis 的全局配置文件mybatis-config.xml
中,通常不需要显式指定每个 XML 文件的路径,因为 MyBatis 会根据 Mapper 接口的全限定名和包扫描机制自动查找对应的 XML 文件。 -
包扫描:如果你使用的是 Spring 或 Spring Boot,可以通过配置来指定 Mapper 接口所在的包,MyBatis 会自动扫描这些包下的接口,并查找对应的 XML 文件。例如,在 Spring Boot 中,你可以在
application.yml
或application.properties
中配置:mybatis:mapper-locations: classpath:com/example/mapper/*.xmltype-aliases-package: com.example.entity
或者在
application.properties
中:mybatis.mapper-locations=classpath:com/example/mapper/*.xml mybatis.type-aliases-package=com.example.entity
3. 命名规范
- 文件名:XML 文件的名称通常与对应的 Mapper 接口名称相同,例如
UserMapper.xml
对应UserMapper
接口。 - 一致性:确保 XML 文件的
namespace
属性与 Mapper 接口的全限定名一致,以便 MyBatis 能够正确地将接口方法与 XML 中的 SQL 语句关联起来。
示例
假设你的项目结构如下:
src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ ├── entity/
│ │ │ └── User.java
│ │ └── mapper/
│ │ └── UserMapper.java
│ └── resources/
│ └── com/
│ └── example/
│ └── mapper/
│ └── UserMapper.xml
在这个结构中,UserMapper.xml
文件位于 src/main/resources/com/example/mapper
目录下,与 UserMapper
接口的包结构一致。
通过这种方式,你可以确保 MyBatis 能够正确加载和解析 XML 映射文件,并与对应的 Mapper 接口关联起来。
实现物理删除的替代方案时,通常不建议直接从数据库中删除记录,而是通过设置一个状态字段(如 is_deleted
或 deleted
)来标记记录是否被“删除”。这种方式称为逻辑删除。以下是如何使用 MyBatis 和通用 Mapper 框架来实现逻辑删除的步骤:
1. 数据库设计
假设你有一个 users
表,其中包含一个 is_deleted
字段,用于标记记录是否被删除。
CREATE TABLE users (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(255) NOT NULL,email VARCHAR(255) NOT NULL,is_deleted TINYINT(1) DEFAULT 0 -- 0表示未删除,1表示已删除
);
2. 实体类
在实体类中添加 isDeleted
字段,并确保它与数据库中的字段对应。
public class User {private Integer id;private String name;private String email;private Boolean isDeleted; // 通常使用Boolean类型,便于逻辑处理// Getters and Setterspublic Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public Boolean getIsDeleted() {return isDeleted;}public void setIsDeleted(Boolean isDeleted) {this.isDeleted = isDeleted;}
}
3. Mapper 接口
使用通用 Mapper 接口 Mapper<T>
,并通过自定义方法实现逻辑删除。
import tk.mybatis.mapper.common.Mapper;public interface UserMapper extends Mapper<User> {// 自定义方法:逻辑删除用户int logicalDeleteUserById(@Param("id") Integer id);
}
4. UserMapper.xml
在 XML 文件中定义 logicalDeleteUserById
方法的 SQL 语句。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.mapper.UserMapper"><!-- 逻辑删除用户 --><update id="logicalDeleteUserById" parameterType="int">UPDATE usersSET is_deleted = 1WHERE id = #{id} AND is_deleted = 0</update></mapper>
5. Service 层实现
在 Service 层调用 UserMapper
的 logicalDeleteUserById
方法。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public boolean deleteUserById(Integer id) {int rowsAffected = userMapper.logicalDeleteUserById(id);return rowsAffected > 0; // 如果影响的行数大于0,表示删除成功}
}
6. 使用示例
在控制器或其他地方调用 UserService
的 deleteUserById
方法。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/users")
public class UserController {@Autowiredprivate UserService userService;@DeleteMapping("/{id}")public String deleteUser(@PathVariable Integer id) {boolean success = userService.deleteUserById(id);return success ? "User deleted successfully" : "Failed to delete user";}
}
说明
- 逻辑删除:通过更新
is_deleted
字段为1
来标记记录为已删除。 - 防止重复删除:在 SQL 语句中添加
AND is_deleted = 0
条件,防止重复删除已标记为删除的记录。 - 通用 Mapper:虽然通用 Mapper 提供了许多基本操作,但某些特定操作(如逻辑删除)需要自定义 SQL。
通过这种方式,你可以在不直接删除数据库记录的情况下,实现记录的“删除”功能,同时保留数据以便后续可能的恢复或审计。
在实现逻辑删除的情况下,查询操作需要排除那些被标记为“已删除”的记录。为了实现这一点,你可以在查询语句中添加一个条件,过滤掉 is_deleted = 1
的记录。
以下是一些常见的查询场景及其实现方式:
1. 查询所有未删除的用户
如果你需要查询所有未被标记为删除的用户,可以在 UserMapper
接口中定义一个自定义查询方法,或者直接使用注解或 XML 配置。
使用 XML 配置
<mapper namespace="com.example.mapper.UserMapper"><!-- 查询所有未删除的用户 --><select id="selectAllActiveUsers" resultType="com.example.model.User">SELECT * FROM users WHERE is_deleted = 0</select></mapper>
使用注解(如果喜欢简洁的代码)
import org.apache.ibatis.annotations.Select;
import tk.mybatis.mapper.common.Mapper;public interface UserMapper extends Mapper<User> {@Select("SELECT * FROM users WHERE is_deleted = 0")List<User> selectAllActiveUsers();
}
2. 根据 ID 查询未删除的用户
如果你需要根据用户的 ID 查询一个未被标记为删除的用户,可以这样做:
使用 XML 配置
<mapper namespace="com.example.mapper.UserMapper"><!-- 根据 ID 查询未删除的用户 --><select id="selectActiveUserById" parameterType="int" resultType="com.example.model.User">SELECT * FROM users WHERE id = #{id} AND is_deleted = 0</select></mapper>
使用注解
import org.apache.ibatis.annotations.Select;
import tk.mybatis.mapper.common.Mapper;public interface UserMapper extends Mapper<User> {@Select("SELECT * FROM users WHERE id = #{id} AND is_deleted = 0")User selectActiveUserById(@Param("id") Integer id);
}
3. 在 Service 层使用
在 Service 层中,你可以调用这些方法来获取未删除的用户。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public List<User> getAllActiveUsers() {return userMapper.selectAllActiveUsers();}public User getActiveUserById(Integer id) {return userMapper.selectActiveUserById(id);}
}
4. 注意事项
- 性能考虑:在大数据量的情况下,频繁查询未删除的记录可能会导致性能问题。可以考虑在数据库中为
is_deleted
字段创建索引,以提高查询性能。 - 通用 Mapper 的限制:虽然通用 Mapper 提供了许多便捷的方法,但在涉及逻辑删除的复杂查询时,可能需要自定义 SQL。
- 数据一致性:确保所有涉及用户数据的查询都包含
is_deleted = 0
的条件,以避免返回已删除的数据。
通过这种方式,你可以在应用中有效地管理逻辑删除,同时保持数据的完整性和可查询性。
解方法可以查出所有未被移除的数据,也能查出单条未被移除的数据,具体取决于方法的设计和实现。以下是详细说明:
1. 查询所有未被移除的数据
通过注解方式实现查询所有未被移除的数据,可以定义一个方法,并在注解中编写对应的 SQL 查询语句。
示例代码
import org.apache.ibatis.annotations.Select;
import java.util.List;public interface UserMapper {// 查询所有未被移除的用户@Select("SELECT * FROM users WHERE is_deleted = 0")List<User> selectAllActiveUsers();
}
-
方法说明
:
selectAllActiveUsers
方法通过注解@Select
指定了 SQL 查询语句。- 查询条件
WHERE is_deleted = 0
确保只返回未被移除的数据。 - 返回值类型为
List<User>
,表示返回多个用户记录。
-
调用方式
:
- 在 Service 层调用
selectAllActiveUsers()
方法,即可获取所有未被移除的用户列表。
- 在 Service 层调用
2. 查询单条未被移除的数据
如果需要查询单条未被移除的数据,可以在注解中添加条件,例如根据用户的 ID 进行查询。
示例代码
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;public interface UserMapper {// 根据 ID 查询未被移除的用户@Select("SELECT * FROM users WHERE id = #{id} AND is_deleted = 0")User selectActiveUserById(@Param("id") Integer id);
}
-
方法说明
:
selectActiveUserById
方法通过注解@Select
指定了 SQL 查询语句。- 查询条件
WHERE id = #{id} AND is_deleted = 0
确保只返回指定 ID 且未被移除的用户。 - 返回值类型为
User
,表示返回单个用户记录。
-
调用方式
:
- 在 Service 层调用
selectActiveUserById(id)
方法,并传入用户 ID,即可获取对应的未被移除的用户。
- 在 Service 层调用
3. 注解方法的灵活性
- 查询所有数据:通过不指定具体条件(除了
is_deleted = 0
),可以返回所有符合条件的记录。 - 查询单条数据:通过添加具体的条件(如
id = #{id}
),可以返回单条符合条件的记录。
4. 示例对比
功能 | 注解方法示例 | 返回类型 |
---|---|---|
查询所有未被移除的数据 | @Select("SELECT * FROM users WHERE is_deleted = 0") List<User> selectAllActiveUsers(); | List<User> |
查询单条未被移除的数据 | @Select("SELECT * FROM users WHERE id = #{id} AND is_deleted = 0") User selectActiveUserById(@Param("id") Integer id); | User |
5. 注意事项
- SQL 注入风险:使用注解时,确保参数通过
#{}
占位符传递,避免直接拼接 SQL 字符串,防止 SQL 注入攻击。 - 性能优化:对于大数据量的查询,建议为
is_deleted
字段创建索引,以提高查询性能。 - 代码可读性:如果查询逻辑较为复杂,建议使用 XML 配置文件编写 SQL,以便更好地维护和管理。
总结
- 注解方法可以同时支持查询所有未被移除的数据和单条未被移除的数据,具体取决于方法的实现。
- 通过在注解中编写不同的 SQL 查询语句,可以灵活地实现各种查询需求。
- 在实际应用中,根据业务需求选择合适的方式(注解或 XML)来实现查询逻辑。
在实现查询所有未被移除(逻辑删除)数据的 impl
(实现类)时,你可以通过以下步骤来完成。假设你使用的是 MyBatis 框架,并且已经有了一个 UserMapper
接口,以下是实现查询所有未被移除用户的具体方法。
1. 定义 UserMapper
接口
首先,确保你的 UserMapper
接口中定义了查询所有未被移除用户的方法。你可以使用注解或 XML 配置来定义这个方法。
使用注解
import org.apache.ibatis.annotations.Select;
import java.util.List;public interface UserMapper {// 查询所有未被移除的用户@Select("SELECT * FROM users WHERE is_deleted = 0")List<User> selectAllActiveUsers();
}
2. 实现 UserMapper
的方法(通常不需要单独实现类,因为 MyBatis 会自动生成代理)
在 MyBatis 中,你通常不需要手动实现 Mapper
接口,因为 MyBatis 会通过动态代理自动生成实现类。不过,如果你使用的是 Spring 框架,并且希望在 Service 层中调用这个方法,你可以按照以下方式编写 Service 层代码。
3. 创建 UserService
类
在 Service 层中,你可以注入 UserMapper
,并调用其方法来获取所有未被移除的用户。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public List<User> getAllActiveUsers() {return userMapper.selectAllActiveUsers();}
}
4. 详细步骤说明
-
定义接口方法
:
- 在
UserMapper
接口中定义selectAllActiveUsers
方法,并使用@Select
注解指定 SQL 查询语句。 - 查询条件
WHERE is_deleted = 0
确保只返回未被移除的用户。
- 在
-
自动生成实现类
:
- MyBatis 会根据
UserMapper
接口自动生成实现类,因此你不需要手动编写实现代码。
- MyBatis 会根据
-
在 Service 层调用
:
- 在
UserService
中注入UserMapper
,并调用selectAllActiveUsers
方法来获取所有未被移除的用户。
- 在
5. 使用 XML 配置(可选)
如果你更喜欢使用 XML 配置而不是注解,可以在 UserMapper.xml
文件中定义查询语句:
<mapper namespace="com.example.mapper.UserMapper"><!-- 查询所有未被移除的用户 --><select id="selectAllActiveUsers" resultType="com.example.model.User">SELECT * FROM users WHERE is_deleted = 0</select></mapper>
6. 注意事项
-
接口与实现
:
- 在 MyBatis 中,
Mapper
接口通常不需要手动实现,MyBatis 会根据接口定义自动生成实现。
- 在 MyBatis 中,
-
注解与 XML
:
- 可以选择使用注解或 XML 配置来定义 SQL 查询,根据项目需求和团队习惯选择合适的方式。
-
数据一致性
:
- 确保所有涉及用户数据的查询都包含
is_deleted = 0
的条件,以避免返回已删除的数据。
- 确保所有涉及用户数据的查询都包含
7. 示例代码结构
// UserMapper.java
public interface UserMapper {@Select("SELECT * FROM users WHERE is_deleted = 0")List<User> selectAllActiveUsers();
}
// UserService.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public List<User> getAllActiveUsers() {return userMapper.selectAllActiveUsers();}
}
8. 总结
- 注解与 XML:你可以选择使用注解或 XML 配置来定义 SQL 查询,MyBatis 会自动处理接口的实现。
- Service 层调用:在 Service 层中调用
UserMapper
的方法,获取未被移除的用户列表。 - 性能优化:对于大数据量查询,建议为
is_deleted
字段创建索引,以提高查询性能。
通过这种方式,你可以在应用中有效地管理逻辑删除,同时保持数据的完整性和可查询性。Service 层提供了业务逻辑的封装,使得调用更加方便和清晰
在实现批量逻辑移除(即将多个记录标记为已删除)的功能时,可以通过 MyBatis 的批量操作来完成。以下是使用 MyBatis 和 Spring 框架实现批量逻辑移除的步骤和示例代码。
1. 定义 UserMapper
接口
首先,在 UserMapper
接口中定义一个方法,用于批量更新用户的 is_deleted
字段。
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import java.util.List;public interface UserMapper {// 批量逻辑移除用户@Update({"<script>","UPDATE users","SET is_deleted = 1","WHERE id IN","<foreach item='id' collection='ids' open='(' separator=',' close=')'>","#{id}","</foreach>","</script>"})int batchDeleteUsers(@Param("ids") List<Integer> ids);
}
2. 说明
@Update
注解:使用 MyBatis 的@Update
注解来定义批量更新操作。<script>
标签:用于在注解中编写动态 SQL。<foreach>
标签:用于遍历ids
集合,生成IN
子句。@Param
注解:用于指定方法参数的名称,以便在 SQL 中引用。
3. 创建 UserService
类
在 Service 层中,注入 UserMapper
,并调用其方法来执行批量逻辑移除。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;@Service
public class UserService {@Autowiredprivate UserMapper userMapper;/*** 批量逻辑移除用户** @param ids 要移除的用户ID列表* @return 受影响的行数*/public int batchDeleteUsers(List<Integer> ids) {if (ids == null || ids.isEmpty()) {return 0;}return userMapper.batchDeleteUsers(ids);}
}
4. 详细步骤说明
-
定义接口方法
:
- 在
UserMapper
接口中定义batchDeleteUsers
方法,并使用@Update
注解指定批量更新的 SQL 语句。 - 使用
<foreach>
标签生成IN
子句,以便在 SQL 中包含多个用户 ID。
- 在
-
自动生成实现类
:
- MyBatis 会根据
UserMapper
接口自动生成实现类,因此你不需要手动编写实现代码。
- MyBatis 会根据
-
在 Service 层调用
:
- 在
UserService
中注入UserMapper
,并调用batchDeleteUsers
方法来执行批量逻辑移除。 - 添加参数检查,确保在
ids
列表为空或为null
时不执行更新操作。
- 在
5. 示例代码结构
// UserMapper.java
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import java.util.List;public interface UserMapper {@Update({"<script>","UPDATE users","SET is_deleted = 1","WHERE id IN","<foreach item='id' collection='ids' open='(' separator=',' close=')'>","#{id}","</foreach>","</script>"})int batchDeleteUsers(@Param("ids") List<Integer> ids);
}
// UserService.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public int batchDeleteUsers(List<Integer> ids) {if (ids == null || ids.isEmpty()) {return 0;}return userMapper.batchDeleteUsers(ids);}
}
6. 注意事项
-
参数检查
:
- 在调用
batchDeleteUsers
方法之前,确保ids
列表不为空或null
,以避免不必要的数据库操作。
- 在调用
-
事务管理
:
- 如果批量逻辑移除是事务的一部分,确保在 Service 层或更高层管理事务,以保证数据一致性。
-
SQL 注入
:
- 使用 MyBatis 的动态 SQL 功能(如
<foreach>
)可以有效防止 SQL 注入。
- 使用 MyBatis 的动态 SQL 功能(如
-
性能考虑
:
- 对于非常大的
ids
列表,考虑分批处理,以避免 SQL 语句过长或数据库性能问题。
- 对于非常大的
通过这种方式,你可以在应用中实现批量逻辑移除功能,同时保持代码的清晰和可维护性。
在 MyBatis 中,<script>
和 <foreach>
是用于动态 SQL 生成的重要标签,它们在处理批量操作时非常有用。以下是它们的作用和参数说明:
1. <script>
标签
-
作用
:
<script>
标签用于在 MyBatis 的注解(如@Select
、@Insert
、@Update
、@Delete
)中编写动态 SQL。- 它允许你在注解中使用复杂的 SQL 逻辑,如条件判断、循环、动态列选择等。
-
使用场景
:
- 当你需要动态生成 SQL 语句时,比如根据输入参数的不同生成不同的 SQL。
- 在批量操作中,用于生成包含多个值的
IN
子句。
2. <foreach>
标签
-
作用
:
<foreach>
标签用于遍历集合(如List
、Set
、数组等),并生成重复的 SQL 片段。- 通常用于生成
IN
子句、批量插入语句等。
-
常用属性
:
collection
:指定要遍历的集合。在注解中,通常使用@Param
注解的参数名。item
:指定集合中每个元素的变量名,在 SQL 片段中通过这个变量名引用当前元素。index
:可选,指定当前元素的索引变量名。open
:可选,指定生成的 SQL 片段的起始字符(如(
)。separator
:可选,指定每个元素之间的分隔符(如,
)。close
:可选,指定生成的 SQL 片段的结束字符(如)
)。
示例说明
@Update({"<script>","UPDATE users","SET is_deleted = 1","WHERE id IN","<foreach item='id' collection='ids' open='(' separator=',' close=')'>","#{id}","</foreach>","</script>"
})
int batchDeleteUsers(@Param("ids") List<Integer> ids);
-
<script>
:
- 包裹整个动态 SQL 语句,使其能够在注解中使用。
-
<foreach>
:
collection='ids'
:指定要遍历的集合是方法参数ids
。item='id'
:指定集合中每个元素的变量名为id
。open='('
:生成的 SQL 片段以(
开头。separator=','
:每个元素之间用,
分隔。close=')'
:生成的 SQL 片段以)
结尾。
-
#{id}
:
- 在 SQL 中引用当前遍历到的元素。
总结
<script>
:用于在注解中编写动态 SQL。<foreach>
:用于遍历集合,生成重复的 SQL 片段,常用于生成IN
子句。
通过这种方式,你可以灵活地生成复杂的 SQL 语句,以适应不同的输入参数和业务需求。
为了实现分页功能,我们需要在 Mapper
接口、Service
层和 Controller
层中进行相应的实现。以下是一个基于 MyBatis
和 Spring Boot
的示例,假设我们有一个 tb_reservation
表。
数据库表结构假设
假设 tb_reservation
表结构如下:
CREATE TABLE tb_reservation (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(100),reservation_date DATE
);
Mapper 接口
首先,我们需要在 Mapper
接口中定义一个分页查询的方法。
public interface ReservationMapper {List<Reservation> selectReservationsWithPagination(@Param("offset") int offset, @Param("limit") int limit);int countReservations();
}
对应的 XML 映射文件(如果使用 XML 映射):
<mapper namespace="com.example.mapper.ReservationMapper"><select id="selectReservationsWithPagination" resultType="com.example.model.Reservation">SELECT * FROM tb_reservation ORDER BY id LIMIT #{limit} OFFSET #{offset}</select><select id="countReservations" resultType="int">SELECT COUNT(*) FROM tb_reservation</select>
</mapper>
Service 层
在 Service
层中,我们实现分页逻辑,包括计算总页数。
@Service
public class ReservationService {@Autowiredprivate ReservationMapper reservationMapper;public Page<Reservation> getReservations(int page, int size) {int offset = (page - 1) * size;List<Reservation> reservations = reservationMapper.selectReservationsWithPagination(offset, size);int total = reservationMapper.countReservations();int totalPages = (int) Math.ceil((double) total / size);return new Page<>(reservations, totalPages);}// 自定义Page类,用于封装分页结果public static class Page<T> {private List<T> content;private int totalPages;public Page(List<T> content, int totalPages) {this.content = content;this.totalPages = totalPages;}// Getters and Setterspublic List<T> getContent() {return content;}public void setContent(List<T> content) {this.content = content;}public int getTotalPages() {return totalPages;}public void setTotalPages(int totalPages) {this.totalPages = totalPages;}}
}
Controller 层
在 Controller
层中,我们实现一个接口来处理分页请求。
@RestController
@RequestMapping("/reservations")
public class ReservationController {@Autowiredprivate ReservationService reservationService;@GetMappingpublic ResponseEntity<ReservationService.Page<Reservation>> getReservations(@RequestParam(defaultValue = "1") int page,@RequestParam(defaultValue = "10") int size) {if (page < 1) {page = 1;}if (size < 1) {size = 10;}ReservationService.Page<Reservation> reservationPage = reservationService.getReservations(page, size);return ResponseEntity.ok(reservationPage);}
}
模型类
确保你有一个 Reservation
模型类来映射数据库表:
public class Reservation {private Integer id;private String name;private Date reservationDate;// Getters and Setterspublic Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Date getReservationDate() {return reservationDate;}public void setReservationDate(Date reservationDate) {this.reservationDate = reservationDate;}
}
说明
- 分页逻辑:
Mapper
接口提供了两个方法,一个用于获取分页数据,另一个用于计算总记录数。Service
层根据这些数据计算总页数,并返回一个Page
对象。 - 分页参数:
Controller
层通过请求参数接收分页参数page
和size
,并进行基本的参数验证。 - 响应格式:
Controller
返回一个包含分页数据和总页数的响应。
通过这种方式,你可以实现一个简单的分页功能,并允许用户通过请求参数进行页面跳转和显示。根据实际需求,你可以进一步扩展和优化这个实现。
在 MyBatis 中,如果你不想使用 XML 映射文件,而是想直接在 Mapper
接口中通过注解来定义 SQL 查询,你可以使用 @Select
注解。下面是如何将你的 XML 映射转换为使用 @Select
注解的示例:
使用 @Select
注解的 Mapper 接口
package com.example.mapper;import com.example.model.Reservation;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;public interface ReservationMapper {/*** 分页查询预订记录** @param offset 偏移量,从0开始* @param limit 每页记录数* @return 预订记录列表*/@Select("SELECT * FROM tb_reservation ORDER BY id LIMIT #{limit} OFFSET #{offset}")List<Reservation> selectReservationsWithPagination(@Param("offset") int offset, @Param("limit") int limit);/*** 查询预订记录总数** @return 预订记录总数*/@Select("SELECT COUNT(*) FROM tb_reservation")int countReservations();
}
说明
-
@Select
注解:
- 使用
@Select
注解直接在接口方法上定义 SQL 查询。 - 注解中的 SQL 字符串可以直接引用方法参数,通过
#{}
语法进行参数绑定。
- 使用
-
@Param
注解:
- 当方法有多个参数时,使用
@Param
注解为每个参数指定一个名称,以便在 SQL 字符串中引用。 - 如果方法只有一个参数,并且参数名与 SQL 中的占位符名一致,可以省略
@Param
注解。
- 当方法有多个参数时,使用
-
返回类型
:
selectReservationsWithPagination
方法返回一个List<Reservation>
,其中Reservation
是与数据库表tb_reservation
对应的实体类。countReservations
方法返回一个int
,表示预订记录的总数。
通过这种方式,你可以在 Mapper
接口中直接定义 SQL 查询,而不需要额外的 XML 映射文件。这种方法适用于简单的查询,对于更复杂的查询或动态 SQL,可能需要结合其他 MyBatis 注解(如 @InsertProvider
、@UpdateProvider
、@DeleteProvider
)或使用 XML 映射文件来提高灵活性。
实现分页模糊查询通常需要结合数据库查询(通过 MyBatis Mapper)、业务逻辑(通过 Service 层)以及 API 接口(通过 Controller 层)。以下是一个基于 Spring Boot 和 MyBatis 的示例,假设我们有一个 User
实体类,并且需要对其 name
字段进行分页模糊查询。
1. 数据库表结构
假设我们有一个 users
表:
CREATE TABLE users (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(255) NOT NULL
);
2. MyBatis Mapper
UserMapper.java
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;public interface UserMapper {@Select("<script>" +"SELECT * FROM users " +"WHERE name LIKE CONCAT('%', #{name}, '%') " +"LIMIT #{limit} OFFSET #{offset}" +"</script>")List<User> findUsersByName(@Param("name") String name, @Param("limit") int limit, @Param("offset") int offset);@Select("<script>" +"SELECT COUNT(*) FROM users " +"WHERE name LIKE CONCAT('%', #{name}, '%')" +"</script>")int countUsersByName(@Param("name") String name);
}
3. Service 层
UserService.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public List<User> findUsersByName(String name, int page, int size) {int offset = (page - 1) * size;return userMapper.findUsersByName(name, size, offset);}public int countUsersByName(String name) {return userMapper.countUsersByName(name);}
}
4. Controller 层
UserController.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;@RestController
@RequestMapping("/users")
public class UserController {@Autowiredprivate UserService userService;@GetMapping("/search")public Map<String, Object> searchUsers(@RequestParam String name,@RequestParam(defaultValue = "1") int page,@RequestParam(defaultValue = "10") int size) {List<User> users = userService.findUsersByName(name, page, size);int total = userService.countUsersByName(name);int totalPages = (int) Math.ceil((double) total / size);Map<String, Object> response = new HashMap<>();response.put("data", users);response.put("total", total);response.put("totalPages", totalPages);response.put("currentPage", page);response.put("size", size);return response;}
}
5. User 实体类
User.java
public class User {private Integer id;private String name;// Getters and Setterspublic Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
说明
-
Mapper 层
:
findUsersByName
方法用于根据名称进行模糊查询,并支持分页。countUsersByName
方法用于获取符合条件的记录总数。
-
Service 层
:
findUsersByName
方法计算偏移量(offset
)并调用 Mapper 层的方法。countUsersByName
方法用于获取记录总数。
-
Controller 层
:
searchUsers
方法接收查询参数(name
、page
、size
),调用 Service 层的方法,并返回分页结果。
-
分页逻辑
:
- 使用
LIMIT
和OFFSET
实现分页。 - 计算总页数
totalPages
,并返回给前端。
- 使用
注意事项
- 确保数据库连接配置正确,并且 MyBatis 已正确集成到项目中。
- 根据实际情况调整实体类字段和数据库表结构。
- 考虑异常处理和输入验证,以提高代码的健壮性。
通过这种方式,你可以实现一个简单的分页模糊查询功能,并通过 RESTful API 提供给前端使用。
实现一个能够处理 Excel 表格导入和导出的工具类、服务层(Service)以及控制器层(Controller)需要涉及多个组件和库。下面是一个简化的实现思路,主要使用 Apache POI 库来处理 Excel 文件。
依赖
首先,确保在你的项目中引入了 Apache POI 的依赖。在 Maven 项目中,你可以在 pom.xml
中添加以下依赖:
<dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>5.2.3</version> <!-- 请使用最新版本 -->
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>5.2.3</version>
</dependency>
ExcelUtil 工具类
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;public class ExcelUtil {public static <T> List<T> importExcel(InputStream is, Class<T> clazz, ExcelRowMapper<T> mapper) throws Exception {List<T> result = new ArrayList<>();try (Workbook workbook = new XSSFWorkbook(is)) {Sheet sheet = workbook.getSheetAt(0); // 假设只处理第一个工作表Iterator<Row> rowIterator = sheet.iterator();boolean isFirstRow = true; // 跳过标题行while (rowIterator.hasNext()) {Row row = rowIterator.next();if (isFirstRow) {isFirstRow = false;continue;}result.add(mapper.mapRow(row));}}return result;}public static <T> void exportExcel(List<T> dataList, String[] headers, OutputStream os, ExcelRowWriter<T> writer) throws Exception {try (Workbook workbook = new XSSFWorkbook()) {Sheet sheet = workbook.createSheet("Data");// 创建标题行Row headerRow = sheet.createRow(0);for (int i = 0; i < headers.length; i++) {Cell cell = headerRow.createCell(i);cell.setCellValue(headers[i]);}// 填充数据for (int i = 0; i < dataList.size(); i++) {Row row = sheet.createRow(i + 1);writer.writeRow(dataList.get(i), row);}workbook.write(os);}}@FunctionalInterfacepublic interface ExcelRowMapper<T> {T mapRow(Row row) throws Exception;}@FunctionalInterfacepublic interface ExcelRowWriter<T> {void writeRow(T data, Row row) throws Exception;}
}
Service 层
假设我们有一个简单的用户实体类 User
。
import org.apache.poi.ss.usermodel.Row;import java.util.List;public class UserService {public List<User> importUsers(InputStream is) throws Exception {return ExcelUtil.importExcel(is, User.class, row -> {// 简单示例,假设Excel列顺序是 id, name, emaillong id = (long) row.getCell(0).getNumericCellValue();String name = row.getCell(1).getStringCellValue();String email = row.getCell(2).getStringCellValue();return new User(id, name, email);});}public void exportUsers(List<User> users, OutputStream os) throws Exception {ExcelUtil.exportExcel(users, new String[]{"ID", "Name", "Email"}, os, (user, row) -> {row.createCell(0).setCellValue(user.getId());row.createCell(1).setCellValue(user.getName());row.createCell(2).setCellValue(user.getEmail());});}
}
Controller 层
使用 Spring Boot 框架来处理 HTTP 请求。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.util.List;@RestController
@RequestMapping("/users")
public class UserController {@Autowiredprivate UserService userService;@PostMapping("/import")public ResponseEntity<String> importUsers(@RequestParam("file") MultipartFile file) {try {List<User> users = userService.importUsers(file.getInputStream());// 处理导入后的用户数据,比如保存到数据库return ResponseEntity.ok("Import successful");} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Import failed: " + e.getMessage());}}@GetMapping("/export")public void exportUsers(HttpServletResponse response) {try {List<User> users = // 获取用户数据,比如从数据库中查询response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=users.xlsx");OutputStream os = response.getOutputStream();userService.exportUsers(users, os);os.flush();} catch (Exception e) {// 处理异常}}
}
注意事项
- 错误处理:实际应用中需要更详细的错误处理。
- 性能优化:对于大数据量的导入导出,可能需要考虑流式处理和分页。
- 安全性:确保上传的文件是安全的,避免文件上传漏洞。
- 数据库交互:示例中没有展示如何与数据库交互,实际应用中需要在导入时将数据保存到数据库,在导出时从数据库读取数据。
- 依赖注入:
UserService
和UserController
示例中使用了 Spring 的依赖注入特性。
这种设计模式使得代码模块化,便于维护和扩展
当然可以!下面是一个假设的 ExcelUtil
工具类的简单示例代码,并附上详细的注释,解释每一行代码的作用。这个工具类通常用于读取或写入 Excel 文件。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;public class ExcelUtil {/*** 读取Excel文件,并将数据存储到List中** @param filePath Excel文件的路径* @return 包含Excel数据的List,每一行是一个List<String>* @throws IOException 如果文件读取失败*/public static List<List<String>> readExcel(String filePath) throws IOException {List<List<String>> data = new ArrayList<>();// 打开Excel文件的输入流try (FileInputStream fis = new FileInputStream(new File(filePath));Workbook workbook = new XSSFWorkbook(fis)) {// 获取第一个工作表Sheet sheet = workbook.getSheetAt(0);// 遍历工作表中的每一行Iterator<Row> rowIterator = sheet.iterator();while (rowIterator.hasNext()) {Row row = rowIterator.next();List<String> rowData = new ArrayList<>();// 遍历行中的每一个单元格Iterator<Cell> cellIterator = row.cellIterator();while (cellIterator.hasNext()) {Cell cell = cellIterator.next();// 根据单元格类型获取单元格的值switch (cell.getCellType()) {case STRING:rowData.add(cell.getStringCellValue());break;case NUMERIC:// 如果是数字类型,可以选择转换为字符串rowData.add(String.valueOf(cell.getNumericCellValue()));break;case BOOLEAN:rowData.add(String.valueOf(cell.getBooleanCellValue()));break;case FORMULA:// 可以选择计算公式结果或公式本身rowData.add(cell.getCellFormula());break;default:rowData.add("");break;}}// 将行数据添加到总数据列表中data.add(rowData);}}return data;}/*** 将数据写入Excel文件** @param filePath Excel文件的路径* @param data 包含要写入的数据的List,每一行是一个List<String>* @throws IOException 如果文件写入失败*/public static void writeExcel(String filePath, List<List<String>> data) throws IOException {try (Workbook workbook = new XSSFWorkbook();FileOutputStream fos = new FileOutputStream(new File(filePath))) {// 创建一个新的工作表Sheet sheet = workbook.createSheet("Sheet1");// 遍历数据列表,将每一行写入工作表for (int i = 0; i < data.size(); i++) {Row row = sheet.createRow(i);List<String> rowData = data.get(i);// 遍历行数据,将每个单元格写入行for (int j = 0; j < rowData.size(); j++) {Cell cell = row.createCell(j);cell.setCellValue(rowData.get(j));}}// 将工作簿写入文件workbook.write(fos);}}
}
代码解释
-
导入必要的库
:
org.apache.poi.ss.usermodel.*
和org.apache.poi.xssf.usermodel.XSSFWorkbook
用于处理 Excel 文件。java.io.*
用于文件输入输出操作。
-
readExcel
方法:
- 打开 Excel 文件并读取其内容。
- 使用
FileInputStream
和XSSFWorkbook
读取 Excel 文件。 - 遍历工作表中的每一行和每一个单元格,根据单元格类型获取其值,并将其存储到
List<List<String>>
中。
-
writeExcel
方法:
- 将数据写入 Excel 文件。
- 创建一个新的
XSSFWorkbook
和Sheet
。 - 遍历数据列表,将每一行写入工作表,并将每个单元格写入行。
- 使用
FileOutputStream
将工作簿写入文件。
希望这些注释能帮助你理解 ExcelUtil
工具类的代码!
ExcelUtil工具类
package com.zmxr.until;import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;import java.io.*;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;public class ExcelUntil<T> {private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");/*** 从Excel文件导入数据到List中** @param file Excel文件* @param clazz 实体类类型* @param dateFormat 日期格式(如果需要解析日期)* @return 实体类对象的List* @throws Exception 异常*/public List<T> importFromExcel(File file, Class<T> clazz, String dateFormat) throws Exception {List<T> list = new ArrayList<>();try (InputStream is = new FileInputStream(file);Workbook workbook = new XSSFWorkbook(is)) {Sheet sheet = workbook.getSheetAt(0);Iterator<Row> rowIterator = sheet.iterator();Row headerRow = rowIterator.next(); // 跳过标题行while (rowIterator.hasNext()) {Row row = rowIterator.next();T obj = clazz.getDeclaredConstructor().newInstance();Field[] fields = clazz.getDeclaredFields();for (int i = 0; i < fields.length; i++) {fields[i].setAccessible(true);Cell cell = row.getCell(i);if (cell != null) {Object value = null;switch (cell.getCellType()) {case STRING:String cellValue = cell.getStringCellValue();if (fields[i].getType() == LocalDate.class) {value = LocalDate.parse(cellValue, DATE_FORMATTER);} else if (fields[i].getType() == LocalDateTime.class) {value = LocalDateTime.parse(cellValue, DATETIME_FORMATTER);} else {value = cellValue;}break;case NUMERIC:if (DateUtil.isCellDateFormatted(cell)) {Date date = cell.getDateCellValue();//如果需要,将Date转换为LocalDateTime(假设特定时间,例如午夜)value = date.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate().atStartOfDay(); // 转换示例// 或者,如果您有时间信息,请使用更精确的转换} else {value = cell.getNumericCellValue();}break;case BOOLEAN:value = cell.getBooleanCellValue();break;// 其他类型可以根据需要添加}fields[i].set(obj, value);}}list.add(obj);}}return list;}/*** 将List数据导出到Excel文件** @param list 数据列表* @param clazz 实体类类型* @param file 输出的Excel文件* @throws Exception 异常*/public void exportToExcel(List<T> list, Class<T> clazz, File file) throws Exception {try (Workbook workbook = new XSSFWorkbook();OutputStream os = new FileOutputStream(file)) {Sheet sheet = workbook.createSheet("Data");// 创建标题行Row headerRow = sheet.createRow(0);Field[] fields = clazz.getDeclaredFields();for (int i = 0; i < fields.length; i++) {fields[i].setAccessible(true);headerRow.createCell(i).setCellValue(fields[i].getName());}// 填充数据行int rowIdx = 1;for (T obj : list) {Row row = sheet.createRow(rowIdx++);for (int i = 0; i < fields.length; i++) {Object value = fields[i].get(obj);Cell cell = row.createCell(i);if (value instanceof Date) {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");cell.setCellValue(sdf.format(value));} else {cell.setCellValue(value != null ? value.toString() : "");}}}workbook.write(os);}}}
根据需要添加
}
fields[i].set(obj, value);
}
}
list.add(obj);
}
}
return list;
}
/*** 将List数据导出到Excel文件** @param list 数据列表* @param clazz 实体类类型* @param file 输出的Excel文件* @throws Exception 异常*/
public void exportToExcel(List<T> list, Class<T> clazz, File file) throws Exception {try (Workbook workbook = new XSSFWorkbook();OutputStream os = new FileOutputStream(file)) {Sheet sheet = workbook.createSheet("Data");// 创建标题行Row headerRow = sheet.createRow(0);Field[] fields = clazz.getDeclaredFields();for (int i = 0; i < fields.length; i++) {fields[i].setAccessible(true);headerRow.createCell(i).setCellValue(fields[i].getName());}// 填充数据行int rowIdx = 1;for (T obj : list) {Row row = sheet.createRow(rowIdx++);for (int i = 0; i < fields.length; i++) {Object value = fields[i].get(obj);Cell cell = row.createCell(i);if (value instanceof Date) {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");cell.setCellValue(sdf.format(value));} else {cell.setCellValue(value != null ? value.toString() : "");}}}workbook.write(os);}
}
}