Spring Boot中如何执行编程式动态拼接的SQL语句
@Query 注解属于Spring Data JPA的静态声明式查询,SQL逻辑在编译期就固定,确实不支持运行时动态拼接。如果只需要简单JDBC能力实现动态查询,不需要额外引入复杂框架,Spring Boot官方自带的组件就能满足需求:
首选方案:
JdbcTemplate(最匹配简单JDBC查询需求)
只要项目引入了spring-boot-starter-jdbc、spring-boot-starter-data-jpa任意一个JDBC相关起步依赖,Spring Boot会自动完成JdbcTemplate的装配,直接注入即可使用。它完全支持手动拼接SQL逻辑,同时提供预编译参数绑定能力避免SQL注入,支持结果自动映射为Map、自定义实体类。
示例代码:@Service public class BizQueryService { private final JdbcTemplate jdbcTemplate; public BizQueryService(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } // 动态查询用户列表示例 public List<User> listUser(Long minId, String nameKeyword) { StringBuilder sql = new StringBuilder("SELECT id, username, create_time FROM sys_user WHERE 1 = 1"); List<Object> queryParams = new ArrayList<>(); // 按需拼接查询条件 if (minId != null) { sql.append(" AND id >= ?"); queryParams.add(minId); } if (nameKeyword != null && !nameKeyword.isBlank()) { sql.append(" AND username LIKE ?"); queryParams.add("%" + nameKeyword + "%"); } // 执行查询,直接映射为User实体 return jdbcTemplate.query( sql.toString(), new BeanPropertyRowMapper<>(User.class), queryParams.toArray() ); } }如果不需要映射实体,直接调用
queryForList方法就能返回List<Map<String, Object>>结构的结果,开发简易接口足够用。备选方案:JPA环境下直接用
EntityManager
如果项目已经深度使用Spring Data JPA,不想额外混用JdbcTemplate,可以直接注入EntityManager执行动态原生SQL:@Repository public class UserRepository { @PersistenceContext private EntityManager entityManager; public List<User> dynamicQuery(Long minId) { StringBuilder sql = new StringBuilder("SELECT * FROM sys_user WHERE 1 = 1"); Map<String, Object> params = new HashMap<>(); if (minId != null) { sql.append(" AND id >= :minId"); params.put("minId", minId); } Query query = entityManager.createNativeQuery(sql.toString(), User.class); params.forEach(query::setParameter); return query.getResultList(); } }
注意:动态拼接SQL时绝对不要直接把用户传入的参数拼接到SQL字符串中,必须用预编译占位符传参,避免SQL注入漏洞。
如果后续动态查询的条件、关联逻辑变得非常复杂,再考虑引入MyBatis的XML动态SQL、或者Spring Data JPA的Specification能力即可,对于当前简易RESTful API的开发需求,上面两个方案足够轻量,不需要额外引入第三方依赖。
内容的提问来源于stack exchange,提问作者Martin Cowie

