Backend Development 10 min read

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.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Secure Your Spring Boot Data with MyBatis Custom TypeHandler Encryption

1. Dependencies and Configuration

Set up a Spring Boot 2.6.12 project with the following Maven dependencies:

<code>&lt;dependencies&gt;
  &lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
  &lt;/dependency&gt;
  &lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;
  &lt;/dependency&gt;
  &lt;dependency&gt;
    &lt;groupId&gt;mysql&lt;/groupId&gt;
    &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
    &lt;scope&gt;runtime&lt;/scope&gt;
  &lt;/dependency&gt;
  &lt;dependency&gt;
    &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt;
    &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt;
    &lt;version&gt;2.1.4&lt;/version&gt;
  &lt;/dependency&gt;
  &lt;dependency&gt;
    &lt;groupId&gt;com.github.pagehelper&lt;/groupId&gt;
    &lt;artifactId&gt;pagehelper-spring-boot-starter&lt;/artifactId&gt;
    &lt;version&gt;1.3.0&lt;/version&gt;
  &lt;/dependency&gt;
&lt;/dependencies&gt;</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&lt;Person&gt; queryPersons();
    int insertPerson(Person person);
}</code>
<code>&lt;mapper namespace="com.pack.mapper.PersonMapper"&gt;
  &lt;resultMap type="com.pack.domain.Person" id="PersonMap"&gt;
    &lt;id column="id" property="id"/&gt;
    &lt;result column="name" property="name"/&gt;
    &lt;result column="id_no" property="idNo" typeHandler="com.pack.mybatis.EncryptTypeHandler"/&gt;
    &lt;result column="create_time" property="createTime"/&gt;
  &lt;/resultMap&gt;
  &lt;select id="queryPersons" resultMap="PersonMap"&gt;
    SELECT * FROM bc_person
  &lt;/select&gt;
  &lt;insert id="insertPerson" parameterType="com.pack.domain.Person"&gt;
    INSERT INTO bc_person (id, name, id_no, create_time) VALUES (#{id}, #{name}, #{idNo, typeHandler=com.pack.mybatis.EncryptTypeHandler}, #{createTime})
  &lt;/insert&gt;
&lt;/mapper&gt;</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.

JavaSpring BootMyBatisencryptionJPACustom TypeHandler
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.