Secure Your Spring Boot Data with MyBatis Custom TypeHandler Encryption
This tutorial demonstrates how to integrate a custom MyBatis TypeHandler in a Spring Boot 2.6.12 application to automatically encrypt sensitive fields before persisting them to MySQL and decrypt them on retrieval, covering dependencies, configuration, entity definition, encryption utilities, mapper XML, and test cases.
1. Dependencies and Configuration
Set up a Spring Boot 2.6.12 project with the following Maven dependencies:
<code><dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
</dependencies></code>Application properties (application.yml) configure the datasource, JPA, PageHelper and MyBatis settings:
<code>spring:
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/testjpa?serverTimezone=GMT%2B8
username: root
password: xxxxx
type: com.zaxxer.hikari.HikariDataSource
hikari:
minimumIdle: 10
maximumPoolSize: 200
autoCommit: true
idleTimeout: 30000
poolName: MasterDatabookHikariCP
maxLifetime: 1800000
connectionTimeout: 30000
connectionTestQuery: SELECT 1
---
spring:
jpa:
generateDdl: false
hibernate:
ddlAuto: update
openInView: true
show-sql: true
---
pagehelper:
helperDialect: mysql
reasonable: true
pageSizeZero: true
offsetAsPageNum: true
rowBoundsWithCount: true
---
mybatis:
type-aliases-package: com.pack.domain
mapper-locations:
- classpath:/mappers/*.xml
configuration:
lazy-loading-enabled: true
aggressive-lazy-loading: false
---
logging:
level:
com.pack.mapper: debug</code>2. Entity Class
<code>@Entity
@Table(name = "BC_PERSON")
public class Person extends BaseEntity {
private String name;
private String idNo;
}</code>JPA will generate the corresponding table.
3. Custom TypeHandler and Encryption Utils
Implement a MyBatis TypeHandler that encrypts values on write and decrypts them on read:
<code>public class EncryptTypeHandler implements TypeHandler<String> {
@Override
public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, EncryptUtils.encrypt(parameter));
}
@Override
public String getResult(ResultSet rs, String columnName) throws SQLException {
String value = rs.getString(columnName);
if (value == null || value.length() == 0) {
return null;
}
return EncryptUtils.decrypt(value);
}
@Override
public String getResult(ResultSet rs, int columnIndex) throws SQLException {
String value = rs.getString(columnIndex);
if (value == null || value.length() == 0) {
return null;
}
return EncryptUtils.decrypt(value);
}
@Override
public String getResult(CallableStatement cs, int columnIndex) throws SQLException {
String value = cs.getString(columnIndex);
if (value == null || value.length() == 0) {
return null;
}
return EncryptUtils.decrypt(value);
}
}</code>The encryption utility uses AES/ECB/PKCS5Padding and a simple hex encoder/decoder:
<code>public class EncryptUtils {
private static final String secretKey = "1111222244445555";
private static final String ALGORITHM = "AES";
public static String encrypt(String data) {
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(secretKey.getBytes(), ALGORITHM));
return Hex.encode(cipher.doFinal(data.getBytes()));
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String decrypt(String secretText) {
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(secretKey.getBytes(), ALGORITHM));
return new String(cipher.doFinal(Hex.decode(secretText)));
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private static class Hex {
private static final char[] HEX = "0123456789abcdef".toCharArray();
public static byte[] decode(CharSequence s) {
int nChars = s.length();
if (nChars % 2 != 0) {
throw new IllegalArgumentException("16进制数据错误");
}
byte[] result = new byte[nChars / 2];
for (int i = 0; i < nChars; i += 2) {
int msb = Character.digit(s.charAt(i), 16);
int lsb = Character.digit(s.charAt(i + 1), 16);
if (msb < 0 || lsb < 0) {
throw new IllegalArgumentException("Detected a Non-hex character at " + (i + 1) + " or " + (i + 2) + " position");
}
result[i / 2] = (byte) ((msb << 4) | lsb);
}
return result;
}
public static String encode(byte[] buf) {
StringBuilder sb = new StringBuilder();
for (byte b : buf) {
sb.append(HEX[(b & 0xF0) >>> 4]).append(HEX[b & 0x0F]);
}
return sb.toString();
}
}
}</code>4. Mapper and XML Mapping
Define a MyBatis mapper interface and the corresponding XML file. The typeHandler attribute points to the custom handler for the encrypted column.
<code>@Mapper
public interface PersonMapper {
List<Person> queryPersons();
int insertPerson(Person person);
}</code> <code><mapper namespace="com.pack.mapper.PersonMapper">
<resultMap type="com.pack.domain.Person" id="PersonMap">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="id_no" property="idNo" typeHandler="com.pack.mybatis.EncryptTypeHandler"/>
<result column="create_time" property="createTime"/>
</resultMap>
<select id="queryPersons" resultMap="PersonMap">
SELECT * FROM bc_person
</select>
<insert id="insertPerson" parameterType="com.pack.domain.Person">
INSERT INTO bc_person (id, name, id_no, create_time) VALUES (#{id}, #{name}, #{idNo, typeHandler=com.pack.mybatis.EncryptTypeHandler}, #{createTime})
</insert>
</mapper></code>5. Test Cases
JUnit tests show how the data is encrypted on insert and decrypted on query.
<code>@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootComprehensiveApplicationTests {
@Resource
private PersonMapper personMapper;
@Test
public void testInsertMapper() {
Person person = new Person();
person.setId("0001");
person.setCreateTime(new Date());
person.setIdNo("111111");
person.setName("中国");
personMapper.insertPerson(person);
}
@Test
public void testQueryUers() {
System.out.println(personMapper.queryPersons());
}
}</code>When inserting, the id_no column is stored encrypted; when querying, it is automatically decrypted.
Encrypted data in the database.
Decrypted data after retrieval.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.