Secure Data with MyBatis: Implement Custom Type Handlers for Encryption in Spring Boot
This tutorial demonstrates how to configure Spring Boot with MyBatis, create a custom TypeHandler that encrypts and decrypts database fields using AES, and integrate the handler in entity mappings and mapper XML for seamless data protection.
Environment: Spring Boot 2.3.9.RELEASE, MyBatis 3.5.6, MySQL, Java 8.
Dependencies and Configuration
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies> spring:
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/testjpa?serverTimezone=GMT%2B8
username: root
password: 123123
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: debugEntity Object
@Entity
@Table(name = "BC_PERSON")
public class Person extends BaseEntity {
private String name ;
private String idNo ;
}JPA is used to generate the corresponding database table.
Custom Type Handler and Encryption Utilities
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);
}
} 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 (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException 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 (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
return null ;
}
}
private static class Hex {
private static final char[] HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
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 (int i = 0, leng = buf.length; i < leng; i++) {
sb.append(HEX[(buf[i] & 0xF0) >>> 4]).append(HEX[buf[i] & 0x0F]) ;
}
return sb.toString() ;
}
}
}Mapper and XML Files
@Mapper
public interface PersonMapper {
List<Person> queryPersons() ;
int insertPerson(Person person) ;
} <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>In the resultMap, the typeHandler attribute points to EncryptTypeHandler so that the column is automatically decrypted when queried. The insert statement also specifies the same handler to encrypt the value before persisting.
Test
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootComprehensiveApplicationTests {
@Resource
private PersonMapper personMapper ;
@Test
public void testInsertMapper() {
com.pack.domain.Person person = new com.pack.domain.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()) ;
}
}When inserting data, the custom type handler encrypts the id_no field automatically.
When querying, the handler decrypts the field so the original value is returned.
The demonstration is complete.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
