前言 Java Spring是目前企业开发中使用较多的一种java开发框架,因此,基于该框架的安全内容尤为重要。Secure CodeWarrior编码实验室的Java Spring涉及到的安全问题有9个,分别为缺少功能级别的访问控制、不恰当的身份验证、日志记录和监控不足、SQL注入、明文存储密码、路径遍历、服务器请求伪造、XML外部实体(XXE)、任意文件上传。 因涉及内容较多,完整内容将会在本公众号拆分为多篇内容分别发出。本文为该系列的第四篇——安全问题四:SQL注入。 往期内容请查看Java Spring编码安全系列。 - <p class="MsoNormal"><span lang="EN-US">Parameterize queries using Java Persistence
- API (JPA)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
复制代码使用 Java Persistence API (JPA) 参数化查询。
- Learn different ways to parameterize
- queries with native SQL queries and Spring Data query methods, in order to
- protect against SQL injection.
复制代码了解使用原生SQL 查询和Spring Data 查询方法参数化查询的不同方法,以防止 SQL 注入。 安全问题四:SQL注入 题目 1、介绍 - <p class="MsoNormal"><span lang="EN-US">The CustomInvoiceRepositoryImpl contains a
- query that is vulnerable to SQL injection (SQLi), as it concatenates
- unvalidated user input in the query string. Dynamically building SQL queries
- with crafted input could return restricted information to the user or
- compromise the database's integrity.One of the most effective ways to combat
- this vulnerability is to use parameterized queries/prepared statements. These
- types of statements pre-compile SQL code with placeholders instead of the
- parameters. When the query is executed, the parameters are added as a separate
- statement.So, DROP what you're doing, and let's JOIN forces in beating SQLi!
- This lab will explore two different ways of writing secure queries.</span></p>
复制代码CustomInvoiceRepositoryImpl 包含一个容易受到 SQL 注入 (SQLi) 攻击的查询,因为它在查询字符串中连接了未经验证的用户输入。使用精心设计的输入动态构建 SQL 查询可能会向用户返回受限信息或损害数据库的完整性。对抗此漏洞的最有效方法之一是使用参数化查询/准备语句。这些类型的语句使用占位符而不是参数来预编译 SQL 代码。执行查询时,参数将作为单独的语句添加。所以,放弃你正在做的事情,让我们联手打败 SQLi!本实验将探索编写安全查询的两种不同方法。
2、源码 - <p class="MsoNormal"><span lang="EN-US">package vikingbank.web;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import jakarta.persistence.EntityManager;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import
- org.springframework.jdbc.core.JdbcTemplate;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import
- org.springframework.stereotype.Repository;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import vikingbank.web.entities.Invoice;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import
- vikingbank.web.repositories.CustomInvoiceRepository;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import java.util.List;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">@Repository<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">public class CustomInvoiceRepositoryImpl
- implements CustomInvoiceRepository {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- private final EntityManager entityManager;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- private final JdbcTemplate jdbcTemplate;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- public CustomInvoiceRepositoryImpl(EntityManager entityManager,
- JdbcTemplate jdbcTemplate) {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- this.entityManager = entityManager;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- this.jdbcTemplate = jdbcTemplate;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> }<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- @Override<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- @SuppressWarnings("unchecked")<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- public List<Invoice> filterSentInvoicesByAccountNumber(String
- bankAccountFilter, long bankAccountId) {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- var query = String.join(" ",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "SELECT i.* FROM invoice
- AS i",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "JOIN bank_account AS ba
- ON i.seller_id = " + bankAccountId + " AND i.buyer_id = ba.Id",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "WHERE ba.account_number
- LIKE '%" + bankAccountFilter + "%'");<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- return entityManager.createNativeQuery(query, Invoice.class)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> .getResultList();<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> }<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- @Override<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- public List<Invoice> filterReceivedInvoicesByAccountNumber(String
- bankAccountFilter, long bankAccountId) {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- var query = String.join(" ",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "SELECT i.* FROM invoice
- AS i",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "JOIN bank_account AS ba
- ON i.seller_id = ba.id AND i.buyer_id = " + bankAccountId + "",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "WHERE ba.account_number
- LIKE '%" + bankAccountFilter + "%'");<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- return jdbcTemplate.query(query, Invoice::fromRow);<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> }<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">}</span></p>
复制代码
*左右滑动查看更多 文件结构如下:
3、步骤一 使用JPA实体管理器 打开分配文件夹中的CustomInvoiceRepositoryImpl.java。VikingBank 的发票有买方和卖方字段。两者都与银行帐号相关联。filterSentInvoicesByAccountNumber创建一个查询,该查询将获取买家银行号码与(部分)银行帐号匹配的所有发票作为过滤器。目前,正在连接用户输入。为了确保其安全,需要使用命名参数对其进行参数化。 3.1 task1 将bankAccountId 连接替换为字符串:bankAccountId。在 Java Persistence API (JPA) 中,:name 表示命名参数。执行查询时,JPA 将在运行时插入用户输入。 BankAccountFilter 变量的参数化有点棘手,因为它是部分匹配。使用 SQL concat 函数对bankAccountFilter 进行参数化。 Hint - <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">"SELECT * FROM table as t WHERE
- t.column1 == :param1 AND t.column2 LIKE CONCAT('%',:param2,'%')"</span></p>
复制代码
根据要求修改代码。 - <p class="MsoNormal"><span lang="EN-US">CustomInvoiceRepositoryImpl.java<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">package vikingbank.web;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import jakarta.persistence.EntityManager;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import
- org.springframework.jdbc.core.JdbcTemplate;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import
- org.springframework.stereotype.Repository;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import vikingbank.web.entities.Invoice;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import
- vikingbank.web.repositories.CustomInvoiceRepository;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import java.util.List;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">@Repository<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">public class CustomInvoiceRepositoryImpl
- implements CustomInvoiceRepository {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- private final EntityManager entityManager;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- private final JdbcTemplate jdbcTemplate;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- public CustomInvoiceRepositoryImpl(EntityManager entityManager,
- JdbcTemplate jdbcTemplate) {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- this.entityManager = entityManager;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- this.jdbcTemplate = jdbcTemplate;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> }<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- /**<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * </span>根据银行账户过滤已发送的发票<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * @param bankAccountFilter </span>银行账户过滤条件<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * @param bankAccountId </span>银行账户<span lang="EN-US">ID<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * @return </span>过滤后的发票列表<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- */<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- @Override<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- @SuppressWarnings("unchecked")<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- public List<Invoice> filterSentInvoicesByAccountNumber(String
- bankAccountFilter, long bankAccountId) {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- var query = String.join(" ",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "SELECT i.* FROM invoice
- AS i",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "JOIN bank_account AS ba
- ON i.seller_id = :bankAccountId AND i.buyer_id = ba.Id",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "WHERE ba.account_number
- LIKE CONCAT('%', :bankAccountFilter, '%')");<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- return entityManager.createNativeQuery(query, Invoice.class)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- .setParameter("bankAccountId", bankAccountId)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- .setParameter("bankAccountFilter", bankAccountFilter)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> .getResultList();<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> }<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- /**<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * </span>根据银行账户过滤已接收的发票<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * @param bankAccountFilter </span>银行账户过滤条件<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * @param bankAccountId </span>银行账户<span lang="EN-US">ID<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * @return </span>过滤后的发票列表<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- */<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- @Override<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- public List<Invoice> filterReceivedInvoicesByAccountNumber(String
- bankAccountFilter, long bankAccountId) {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- var query = String.join(" ",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "SELECT i.* FROM invoice
- AS i",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "JOIN bank_account AS ba
- ON i.seller_id = ba.id AND i.buyer_id = :bankAccountId",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "WHERE ba.account_number
- LIKE CONCAT('%', :bankAccountFilter, '%')");<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- return jdbcTemplate.query(query, Invoice::fromRow,<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> bankAccountId,
- bankAccountFilter);<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> }<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">}<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">*</span>左右滑动查看更多<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">3.2 task2<o:p></o:p></span></p>
- <p class="MsoNormal">命名参数仍然需要值,以便<span lang="EN-US">entityManager </span>可以准备和执行查询。将<span lang="EN-US"> setParameter </span>调用链接到每个参数的<span lang="EN-US"> createNativeQuery </span>调用上。<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">setParameter </span>调用采用两个参数,第一个是不带<span lang="EN-US">: </span>的命名参数,第二个是值。<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">.setParameter("name", value)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">Step Solution<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">var query = String.join(" ",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- "SELECT i.* FROM invoice AS i",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- "JOIN bank_account AS ba ON i.seller_id = :accountId AND i.buyer_id
- = ba.Id",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- "WHERE ba.account_number LIKE CONCAT('%',:filter,'%')");<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">return
- entityManager.createNativeQuery(query, Invoice.class)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- .setParameter("accountId", bankAccountId)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- .setParameter("filter", bankAccountFilter)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- .getResultList()</span><span style="background-color: rgb(255, 255, 255);">;</span></p>
复制代码
按照要求修改: - <p class="MsoNormal"><span lang="EN-US">CustomInvoiceRepositoryImpl.java<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">package vikingbank.web;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import jakarta.persistence.EntityManager;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import
- org.springframework.jdbc.core.JdbcTemplate;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import
- org.springframework.stereotype.Repository;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import vikingbank.web.entities.Invoice;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import
- vikingbank.web.repositories.CustomInvoiceRepository;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import java.util.List;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">@Repository<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">public class CustomInvoiceRepositoryImpl
- implements CustomInvoiceRepository {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- private final EntityManager entityManager;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- private final JdbcTemplate jdbcTemplate;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- public CustomInvoiceRepositoryImpl(EntityManager entityManager,
- JdbcTemplate jdbcTemplate) {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- this.entityManager = entityManager;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- this.jdbcTemplate = jdbcTemplate;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> }<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- /**<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * </span>根据银行账户过滤已发送的发票<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- *<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * @param bankAccountFilter </span>银行账户过滤条件<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * @param bankAccountId </span>银行账户<span lang="EN-US">ID<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * @return </span>过滤后的发票列表<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- */<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- @Override<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- @SuppressWarnings("unchecked")<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- public List<Invoice> filterSentInvoicesByAccountNumber(String
- bankAccountFilter, long bankAccountId) {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- var query = String.join(" ",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "SELECT i.* FROM invoice
- AS i",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "JOIN bank_account AS ba
- ON i.seller_id = :bankAccountId AND i.buyer_id = ba.Id",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "WHERE ba.account_number
- LIKE CONCAT('%', :bankAccountFilter, '%')");<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- return entityManager.createNativeQuery(query, Invoice.class)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- .setParameter("bankAccountId", bankAccountId)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- .setParameter("bankAccountFilter", bankAccountFilter)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> .getResultList();<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> }<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- /**<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * </span>根据银行账户过滤已接收的发票<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- *<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * @param bankAccountFilter </span>银行账户过滤条件<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * @param bankAccountId </span>银行账户<span lang="EN-US">ID<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * @return </span>过滤后的发票列表<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- */<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- @Override<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- public List<Invoice> filterReceivedInvoicesByAccountNumber(String
- bankAccountFilter, long bankAccountId) {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- var query = String.join(" ",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "SELECT i.* FROM invoice
- AS i",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "JOIN bank_account AS ba
- ON i.seller_id = ba.id AND i.buyer_id = :bankAccountId",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "WHERE ba.account_number
- LIKE CONCAT('%', :bankAccountFilter, '%')");<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- var jpaQuery = entityManager.createNativeQuery(query, Invoice.class);<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- jpaQuery.setParameter("bankAccountId", bankAccountId);<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- jpaQuery.setParameter("bankAccountFilter", bankAccountFilter);<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- return jpaQuery.getResultList();<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> }<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">}</span></p>
复制代码
*左右滑动查看更多 分析一下代码内容。 该类是自定义发票仓库的实现类,实现了 CustomInvoiceRepository 接口。以下是对代码的详细注释: @Repository 注解标记该类为仓库组件。 定义了 CustomInvoiceRepositoryImpl 类,并声明了 EntityManager 和 JdbcTemplate 成员变量,并在构造函数中进行依赖注入。 @Override 注解表示该方法重写了接口中的方法。 filterSentInvoicesByAccountNumber 方法用于根据银行账户过滤已发送的发票。 filterReceivedInvoicesByAccountNumber 方法用于根据银行账户过滤已接收的发票。 @SuppressWarnings("unchecked") 注解用于抑制类型转换警告。 在 filterSentInvoicesByAccountNumber 方法中,使用原生 SQL 查询语句拼接查询,并通过 EntityManager 执行查询,并将结果转换为 Invoice 实体对象。 在 filterReceivedInvoicesByAccountNumber 方法中,使用原生 SQL 查询语句拼接查询,并通过 EntityManager 执行查询,并将结果转换为 Invoice 实体对象。 以上就是对上面代码的详细注释,该类实现了自定义的发票仓库接口,并提供了根据银行账户进行发票过滤的功能。 4、步骤二 使用Jdbc模板
接下来转到 filterReceivedInvoicesByAccountNumber 方法。此方法将查询卖方银行号码与(部分)银行帐号匹配的所有发票作为过滤器。该查询连接了也需要参数化的用户输入。这次,使用Spring框架组件JdbcTemplate来实现这一点。 4.1 task1 JdbcTemplate 使用位置参数来参数化查询。位置参数由 ? 表示。特点。将连接的变量替换为该字符。 与上一步一样,使用 concat 函数参数化bankAccountFilter。 按照要求修改代码。 - <p class="MsoNormal"><span lang="EN-US">@Override<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- public List<Invoice> filterReceivedInvoicesByAccountNumber(String
- bankAccountFilter, long bankAccountId) {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- var query = String.join(" ",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "SELECT i.* FROM invoice
- AS i",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "JOIN bank_account AS ba
- ON i.seller_id = ba.id AND i.buyer_id = ?",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> "WHERE ba.account_number
- LIKE CONCAT('%', ?, '%')");<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- return jdbcTemplate.query(query, Invoice::fromRow, bankAccountId,
- bankAccountFilter);<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> }</span></p>
复制代码
*左右滑动查看更多 4.2 task2 将 BankAccountId 和bankAccountFilter作为参数添加到现有查询方法中。由于 jdbcTemplate 使用位置参数,因此它们的传递顺序很重要。 - <p class="MsoNormal"><span lang="EN-US">Step Solution<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">var query = String.join(" ",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- "SELECT i.* FROM invoice AS i",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- "JOIN bank_account AS ba ON i.seller_id = ba.id AND i.buyer_id =
- ?",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- "WHERE ba.account_number LIKE CONCAT('%',?,'%')");<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">return jdbcTemplate.query(query,
- Invoice::fromRow, bankAccountId, bankAccountFilter);</span></p>
复制代码
*左右滑动查看更多 整理后得到: - <p class="MsoNormal"><span lang="EN-US">@Override<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- public List<Invoice> filterReceivedInvoicesByAccountNumber(String
- bankAccountFilter, long bankAccountId) {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- var query = String.join(" ",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- "SELECT i.* FROM invoice AS i",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- "JOIN bank_account AS ba ON i.seller_id = ba.id AND i.buyer_id =
- ?",<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- "WHERE ba.account_number LIKE CONCAT('%',?,'%')");<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- return jdbcTemplate.query(query, Invoice::fromRow, bankAccountId,
- bankAccountFilter);<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> }</span></p>
复制代码
*左右滑动查看更多 提交通过,分析一下: 该方法是重写了接口中的filterReceivedInvoicesByAccountNumber 方法,用于根据银行账户过滤已接收的发票。 使用 @Override 注解表示该方法是对接口方法的重写。 query 是用于构建 SQL 查询语句的字符串,使用 String.join 方法将多个字符串拼接为一个完整的查询语句。 查询语句使用了 JOIN 来关联invoice 表和 bank_account 表,并通过seller_id 和 buyer_id 进行匹配。 WHERE 子句使用 LIKE 来模糊匹配银行账户号码。 jdbcTemplate.query 方法执行查询,并将结果转换为 Invoice 对象列表,使用 Invoice::fromRow 方法进行转换。 该方法通过 JdbcTemplate 执行 SQL 查询,根据指定的银行账户过滤条件,返回符合条件的已接收发票列表。 5、步骤三 使用生成的查询 本机查询现在是安全、整洁的,接下来继续下一步。Spring Data 可以根据方法名称中特定关键字的使用来派生查询。第一部分应该是动作动词,例如 find、read、get... 名称的其余部分是使用条件关键字和与查询实体相关的属性名称构造的条件。例如 getTransactionById。 5.1 task1 浏览至空接口 TransactionRepository.java。存储库需要执行 CRUD(创建、读取、更新、删除)操作,因此: 使用 CrudRepository<T, Id> 扩展它。 1、其中第一个类型参数是数据库实体Transaction。 2、第二个是 ID 类型:Transaction 有一个 Long 作为 ID。 - <p class="MsoNormal"><span lang="EN-US">Task Solution<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">public interface TransactionRepository
- extends CrudRepository<Transaction, Long> {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> //
- query method<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">}</span></p>
复制代码
*左右滑动查看更多 - <p class="MsoNormal"><span lang="EN-US">TransactionRepository.java<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">package vikingbank.web;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">public interface TransactionRepository {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">}<o:p></o:p></span></p>
- <p class="MsoNormal"></p>
复制代码
*左右滑动查看更多 按要求修改为以下代码: - <p class="MsoNormal"><span lang="EN-US">TransactionRepository.java</span>源码<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">package vikingbank.web;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import
- org.springframework.data.repository.CrudRepository;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import jakarta.transaction.Transaction;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">public interface TransactionRepository
- extends CrudRepository<Transaction, Long> {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> //
- query method<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">}</span></p>
复制代码
*左右滑动查看更多 5.2 task2 在此接口内,创建一个返回事务列表的查询方法,其中: 根据OwnerAccountId 查找交易。 金额介于两个值之间。 根据最新的时间戳对这些交易进行排序。 此方法需要三个参数:id、金额的最小值和最大值。 Task Solution - List<Transaction>
- findByOwnerAccountIdAndAmountIsBetweenOrderByTimestampDesc(long id, double min,
- double max);
复制代码
*左右滑动查看更多 - <p class="MsoNormal"><span lang="EN-US">Step Solution<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import java.util.List;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import
- org.springframework.data.repository.CrudRepository;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import vikingbank.web.entities.Transaction;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">public interface TransactionRepository
- extends CrudRepository<Transaction, Long> {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- List<Transaction>
- findByOwnerAccountIdAndAmountIsBetweenOrderByTimestampDesc(long id, double min,
- double max);<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">}</span></p>
复制代码
*左右滑动查看更多 按照要求修改一下: - <p class="MsoNormal"><span lang="EN-US">TransactionRepository.java<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">package vikingbank.web;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import java.util.List;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import
- org.springframework.data.repository.CrudRepository;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">import vikingbank.web.entities.Transaction;<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> <o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">public interface TransactionRepository
- extends CrudRepository<Transaction, Long> {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- /**<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * </span>根据所属账户<span lang="EN-US">ID</span>和金额范围进行查询,并按照时间戳降序排序<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- *<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * @param id </span>所属账户<span lang="EN-US">ID<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * @param min </span>最小金额<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * @param max </span>最大金额<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- * @return </span>符合条件的交易列表<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- */<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">
- List<Transaction>
- findByOwnerAccountIdAndAmountIsBetweenOrderByTimestampDesc(long id, double min,
- double max);<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US">}</span></p>
复制代码
*左右滑动查看更多 提交通过,分析一下。 该接口继承自 CrudRepository 接口,用于操作 Transaction 实体对象的数据访问和持久化。 findByOwnerAccountIdAndAmountIsBetweenOrderByTimestampDesc方法用于根据所属账户ID和金额范围进行查询,并按照时间戳降序排序。 参数说明: id:所属账户ID,用于匹配交易的所属账户ID。 min:最小金额,用于匹配交易的金额范围下限。 max:最大金额,用于匹配交易的金额范围上限。 返回值:符合条件的交易列表。 该接口提供了通过所属账户ID和金额范围进行查询交易的功能,并按照时间戳降序排序返回结果。 总结 在构建 SQL 查询时,避免使用未经验证的用户输入进行字符串拼接。即使在确保数据安全的情况下,也要始终避免在查询中使用拼接。应该使用参数化查询或使用特定框架的 API 来自动处理这一过程。
|