How to Prevent SQL Injection in Java: JDBC, MyBatis, JPA & Hibernate Best Practices

This article reviews Java persistence technologies, shows how raw string concatenation in JDBC, MyBatis, JPA/Hibernate and native SQL can lead to SQL injection, and provides concrete safe‑coding patterns such as PreparedStatement, #{ } bindings, named parameters, and dynamic‑SQL safeguards.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
How to Prevent SQL Injection in Java: JDBC, MyBatis, JPA & Hibernate Best Practices

Overview

SQL injection occurs when user‑supplied data is concatenated into a SQL string and sent to the database. In Java persistence layers the risk appears in plain JDBC, MyBatis (XML/annotation), JPA/Hibernate (HQL or native SQL) and native queries. The safe pattern is to separate code from data by using parameterised statements or by validating any fragment that must be inserted literally.

JDBC

Vulnerable code builds the statement with string concatenation:

String sql = "SELECT * FROM users WHERE name ='" + name + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);

Secure code uses PreparedStatement with ? placeholders. The statement is prepared before any user data is added, so the database parses the SQL once and later binds the values as data, preventing re‑parsing of malicious input.

String sql = "SELECT * FROM users WHERE name = ?";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1, name);
ResultSet rs = ps.executeQuery();

Preparing a statement after the SQL string has already been built does not remove the injection risk.

MyBatis

MyBatis maps Java methods to SQL defined in XML or annotations. Two placeholder syntaxes exist: #{} – generates a PreparedStatement and binds the value safely. ${} – performs literal string substitution and can re‑introduce injection.

Safe XML mapping (parameter bound with #{} ) :

<select id="getById" resultType="org.example.User">
    SELECT * FROM user WHERE id = #{id}
</select>

Vulnerable XML mapping (literal substitution with ${} ) :

<select id="getByName" resultType="org.example.User">
    SELECT * FROM user WHERE name = '${name}' LIMIT 1
</select>

When a fragment such as an ORDER BY column cannot be bound with #{}, apply a whitelist before substitution. MyBatis provides <choose> / <when> / <otherwise> to implement the whitelist:

<select id="getUserListSortBy" resultType="org.example.User">
    SELECT * FROM user
    <choose>
        <when test="sortBy == 'name' or sortBy == 'email'">
            ORDER BY ${sortBy}
        </when>
        <otherwise>
            ORDER BY name
        </otherwise>
    </choose>
</select>

Other dynamic constructs such as LIKE with wildcards, IN clauses using <foreach>, and pagination with LIMIT should also use #{} for values and whitelist any raw fragments.

JPA & Hibernate

Both APIs expose HQL/JPQL (object‑oriented query language) and native SQL. Concatenating parameters into the query string is vulnerable:

Query<User> q = session.createQuery(
    "from User where name = '" + name + "'"
);
User u = q.getSingleResult();

Secure alternatives:

Positional parameter: ? with setParameter(int, value).

Named parameter: :name with setParameter("name", value).

Parameter list for IN clauses using setParameterList.

Bean property binding with setProperties(bean).

// Positional
Query<User> q = session.createQuery(
    "from User where name = ?", User.class);
q.setParameter(0, name);

// Named
Query<User> q2 = session.createQuery(
    "from User where name = :name", User.class);
q2.setParameter("name", name);

// List parameter
Query<User> q3 = session.createQuery(
    "from User where name in (:nameList)", User.class);
q3.setParameterList("nameList",
    Arrays.asList("lisi", "zhaowu"));

// Bean binding
User bean = new User();
bean.setName("zhaowu");
Query<User> q4 = session.createQuery(
    "from User where name = :name", User.class);
q4.setProperties(bean);

Native SQL via JPA/Hibernate

Native queries suffer the same injection risk as plain JDBC. Use named parameters with createNativeQuery:

String sql = "select * from user where name = :name";
Query query = session.createNativeQuery(sql);
query.setParameter("name", name);

Key mitigation strategies

Never concatenate untrusted input into SQL strings.

Prefer PreparedStatement, MyBatis #{}, or JPA named/positional parameters.

When a literal fragment (e.g., column name, ORDER BY) must be inserted, validate it against a whitelist or use an enum‑based mapping.

For LIKE patterns, add wildcards to the bound value (e.g., ps.setString(1, "%" + term + "%")) or use MyBatis <bind> to construct the pattern safely.

Use <foreach> with #{} for IN lists to avoid manual string joining.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaMyBatisSecurityJDBCSQL injectionHibernatejpa
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

0 followers
Reader feedback

How this landed with the community

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.