TkMyBatis扩展
TkMyBatis是一款MyBatis扩展框架,它基于MyBatis实现了很多高级ORM功能,使得我们使用MyBatis框架时,既能获得部分类似Hibernate这种全功能ORM的开发体验,也不失MyBatis的灵活性。这篇笔记我们基于SpringBoot 3.x介绍TkMyBatis框架的使用。
项目主页:https://mybatis.io/
类似的框架还有MyBatis-Plus,这两个框架功能和定位类似,但细节上有一些区别。
引入Maven依赖和工程配置
TkMyBatis提供了SpringBoot的起步依赖,我们直接引入即可。注意如果你使用的是较早版本的SpringBoot,版本号可能有区别。
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>5.0.2</version>
</dependency>
此外,上一篇笔记中介绍的分页插件PageHelper也是同一作者的项目,它和TkMyBatis完全兼容,一般都是搭配使用。
通用CRUD接口
TkMyBatis最强大的功能就是通用CRUD接口,我们的Mapper接口继承BaseMapper后,可以不编写XML映射文件就能实现基本的CRUD功能。BaseMapper有许多通用的方法,我们可以直接调用这些方法来进行基本的CRUD操作,下面我们直接看一个例子。
定义数据模型
实体类代码中,我们需要用JPA注解标注相关的类和字段,这部分内容可以参考JPA相关章节,TkMyBatis也会读取这些注解并自动进行字段和表的映射。
User.java
package com.gacfox.demo.model;
import jakarta.persistence.*;
import lombok.Data;
import java.io.Serializable;
@Data
@Table(name = "t_user")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id")
private Long userId;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
}
如果实体类中定义了某些纯辅助型、不需要与数据库字段映射的成员变量,它必须加上@Transient注解,否则TkMyBatis在拼装SQL时会因找不到列而报错。
@Transient
private String tempToken;
另一个常见问题是如果列名为关键字,如key、desc等,@Column注解的name属性中也要使用反引号包裹,否则SQL执行时会报错!
@Column(name = "`key`")
private String key;
定义Mapper接口
TkMyBatis中的Mapper代码可以做到非常简洁,我们直接继承了一个BaseMapper,其中用泛型指定了实体类,此外我们没有任何的实现代码。
UserMapper.java
package com.gacfox.demo.mapper;
import com.gacfox.demo.model.User;
import org.apache.ibatis.annotations.Mapper;
import tk.mybatis.mapper.common.BaseMapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
调用通用CRUD接口
下面代码使用Mapper时,我们直接调用了通用Mapper中的selectByPrimaryKey方法,它是一个根据主键获取实体类对象的通用方法。
UserServiceImpl.java
package com.gacfox.demo.service.impl;
import com.gacfox.demo.dto.UserDTO;
import com.gacfox.demo.mapper.UserMapper;
import com.gacfox.demo.model.User;
import com.gacfox.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
@Autowired
public UserServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public UserDTO getUserById(Long userId) {
User user = userMapper.selectByPrimaryKey(userId);
return toUserDTO(user);
}
private UserDTO toUserDTO(User user) {
UserDTO dto = new UserDTO();
dto.setUserId(user.getUserId());
dto.setUsername(user.getUsername());
dto.setPassword(user.getPassword());
return dto;
}
}
除了selectByPrimaryKey,还有许多其他好用的通用方法,这里就不列举了。
Criteria查询
除了通用Mapper,TkMyBatis也支持Criteria方式构建查询。
package com.gacfox.demo.mapper;
import com.gacfox.demo.model.User;
import org.apache.ibatis.annotations.Mapper;
import tk.mybatis.mapper.common.BaseMapper;
import tk.mybatis.mapper.common.ExampleMapper;
@Mapper
public interface UserMapper extends BaseMapper<User>, ExampleMapper<User> {
}
这里我们的Mapper继承了ExampleMapper,它能够使得我们的Mapper接收TkMyBatis提供的Example对象。
package com.gacfox.demo.service.impl;
import com.gacfox.demo.dto.UserDTO;
import com.gacfox.demo.mapper.UserMapper;
import com.gacfox.demo.model.User;
import com.gacfox.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;
@Service("userService")
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
@Autowired
public UserServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public UserDTO getUserById(Long userId) {
Example example = new Example(User.class);
example.createCriteria().andEqualTo("userId", userId);
User user = userMapper.selectOneByExample(example);
return toUserDTO(user);
}
private UserDTO toUserDTO(User user) {
UserDTO dto = new UserDTO();
dto.setUserId(user.getUserId());
dto.setUsername(user.getUsername());
dto.setPassword(user.getPassword());
return dto;
}
}
代码比较简单,我们直接构造了一个条件查询的Criteria,使用Mapper查询即可。这里除了andEqualTo还有很多方法可用,这里就不展开介绍了。
此外有一点尤其要注意,Criteria有一个方法criteria.andCondition(),我们在使用这个方法的使用千万不要用字符串拼接SQL,错误示例:criteria.andCondition("id='" + id + "'"),这种写法存在严重的SQL注入漏洞,如果id字段为用户传入,黑客是可以通过SQL注入方式直接下载整个数据库的!正确示例:criteria.andCondition("id=", id)。