【发布时间】:2020-07-04 11:15:36
【问题描述】:
我有一个带有 h2 数据库的 spring mvc 应用程序。 Db 对象使用 JPA 标准和 spring @Transaction 注释进行更新。请参阅底部的 findBySymbol() 和 update()。
update() 完成后,应用调用 findBySymbol() 对更新的记录运行一些逻辑。 findBySymbol() SQL 是正确的。问题是 findBySymbol() 的结果没有被更新。这很令人费解,因为 Hibernate 跟踪日志显示了更新绑定参数、SQL 更新语句和 executeUpdate() 的 int 结果。同样令人困惑的是,我当时可以直接查询数据库并查看记录实际上是否已更新。
我需要做什么才能让应用在更新后立即查询数据库并返回更新的数据?
import com.example.mycompany.beans.AEarnings;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import org.springframework.stereotype.Repository;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.CriteriaUpdate;
import javax.persistence.NoResultException;
import org.springframework.transaction.annotation.Transactional;
@Repository
public class AnnualEarningsDaoImpl implements AnnualEarningsDao {
@PersistenceContext
EntityManager entityManager;
@Override
public List<AEarnings> findBySymbol(String s) {
// CriteriaBuilder
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
// CriteriaQuery
CriteriaQuery<AEarnings> cQuery = cb.createQuery(AEarnings.class);
// Root
Root<AEarnings> ae = cQuery.from(AEarnings.class);
// Parameters
ParameterExpression<String> p = cb.parameter(String.class);
// Db command
cQuery.select(ae).where(cb.equal(ae.get("symbol"), p));
// TypedQuery
TypedQuery<AEarnings> query = entityManager.createQuery(cQuery);
// Set Parameter
query.setParameter(p, s);
// Results
List<AEarnings> results = query.getResultList();
// Return
return results;
}
@Override
@Transactional
public void update(
String a, LocalDate b, String c,
BigDecimal d, BigDecimal e, BigDecimal f) {
// CriteriaBuilder
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
// CriteriaUpdate<AEarnings>
CriteriaUpdate<AEarnings> update
= builder.createCriteriaUpdate(AEarnings.class);
// Root
Root<AEarnings> root = update.from(AEarnings.class);
// Holds parameters
List<Predicate> predicates = new ArrayList<>();
// Predicates
predicates.add(builder.equal(root.get("a"), a));
predicates.add(builder.equal(root.get("b"), b));
predicates.add(builder.equal(root.get("c"), c));
// Update
update
.set(root.get("d"), d)
.set(root.get("e"), e)
.set(root.get("f"), f)
.where(predicates.toArray(new Predicate[]{}));
// Execute
int i = entityManager.createQuery(update).executeUpdate();
System.out.println("Updated: " + i);
}
}
使用 AEarningsServiceImpl 更新,在这种情况下只调用 dao 方法:
import com.example.mycompany.beans.AEarnings;
import com.example.mycompany.dao.AEarningsDao;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AEarningsServiceImpl implements AEarningsService{
@Autowired
AEarningsDao aEarningsDao;
@Override
public List<AnnualEarnings> findBySymbol(String sym) {
return annualEarningsDao.findBySymbol(sym);
}
@Override
@Transactional
public void update(
String symbol, LocalDate fiscalEndDate, String fiscalPeriod,
BigDecimal prev, BigDecimal diff, BigDecimal diffPercent) {
annualEarningsDao.update(symbol, fiscalEndDate, fiscalPeriod,
prev, diff, diffPercent);
}
// save()...
// delete()...
}
用 pom.xml 更新:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany</groupId>
<artifactId>appmaker</artifactId>
<version>0.0.1-SNAPSHOT</version>
<description>Spring Boot Stock App</description>
<name>app</name>
<properties>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>8.0.5</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId>
<version>3.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
更新了调用 AEarningsService 的代码:
package com.example.mycompany.beans.helpers;
import com.example.mycompany.beans.AnnualEarnings;
import com.example.mycompany.beans.Token;
import com.example.mycompany.beans.Url;
import com.example.mycompany.service.AnnualEarningsService;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class AnnualHelper {
private static final BigDecimal HUNDRED = new BigDecimal(100);
private static final String EARNINGS_4_TAG = "/earnings/4?period=annual&token=";
private static final String EARNINGS_1_TAG = "/earnings/1?period=annual&token=";
private Url url;
@Autowired
private AnnualEarningsService annualEarningsService;
// Call class methods, filter for earnings of size > 0 for initial run
public List<AnnualEarnings> run(String domain, String token, BigDecimal percent) {
// Holds results
List<AnnualEarnings> results = new ArrayList<>();
// Grab symbols
...
// Loop through incoming symbols
symbols.forEach((symbol) -> {
// Grab most recent fiscalPeriod for symbol
List<AnnualEarnings> exists = annualEarningsService.findBySymbol(symbol);
// Check if symbol is in db
// If exists, log
if (!exists.isEmpty()) {
log(...);
// Else, process new symbol
} else {
try {
List<AnnualEarnings> earnings = processEarnings(symbol, percent);
// Filter for List<AnnualEarnings> greater than zero, add to results
if (!earnings.isEmpty()) {
// Add to results
earnings.forEach(e -> results.add(e));
}
} catch (Exception ex) {
logInfo("runAEInitial() - Exception: ", ex);
}
}
});
return results;
}
// For one symbol, parse, save, update initial earnings
private List<AnnualEarnings> processEarnings(String symbol, BigDecimal percent) {
// Create Url
...
// Grab api response
String response = "";
try {
response = CommonUtils.callApi(url);
} catch (IOException ex) {
log(...);
}
// Catch empty json object returned from API
if (response.length() == 2) {
return Collections.emptyList();
}
// Parse json from api, save to db
parseAndSaveEarnings(response);
// Update AnnualEarnings Objects
updateEarnings(symbol);
// From db, grab symbol's eearnings for last 4 quarters
List<AnnualEarnings> updatedEarnings = annualEarningsService.findBySymbol(symbol);
return filterByDiffPercent(updatedEarnings, percent);
}
// Update previous, diff values of AnnualEarnings objects just created
private void updateEarnings(String symbol) {
// Grab all objects from db for symbol
List<AnnualEarnings> inList =
annualEarningsService.findBySymbol(symbol);
// Update prev & diff values of incoming AnnualEarnings object
for (int i = 1; i < inList.size(); i++) {
// Existing object fiscalEndDate
LocalDate fiscalEndDate = inList.get(i - 1).getFiscalEndDate();
// Existing object fiscalPeriod
String fiscalPeriod = inList.get(i - 1).getFiscalPeriod();
// Get original
BigDecimal current = inList.get(i - 1).getActualEPS();
// Get previous
BigDecimal prev = inList.get(i).getActualEPS();
// Get diff
BigDecimal diff = current.subtract(prev);
// Get diffPercent
BigDecimal diffPercent = (diff.divide(prev, 4, RoundingMode.HALF_EVEN)).movePointRight(2);
// Align num signs
diffPercent = CommonUtils.alignSigns(diff, diffPercent);
// Update
annualEarningsService.update(symbol, fiscalEndDate, fiscalPeriod, prev, diff, diffPercent);
}
}
// Parse json string returned from API, save to db
private void parseAndSaveEarnings(String str) {
// Create parser
JSONParser parser = new JSONParser();
JSONObject fullObj;
try {
// Full json object
fullObj = (JSONObject) parser.parse(str);
// First layer
JSONArray earningsArray = (JSONArray) fullObj.get("earnings");
// Symbol
String jsonSymbol = fullObj.get("symbol").toString();
// Loop through earnings for one symbol
earningsArray.forEach((earningsJson) -> {
logInfo("", "");
// Create jsonobject
JSONObject jsonObject = (JSONObject) earningsJson;
// Grab values
String jsonFiscEndDateStr = jsonObject.get("fiscalEndDate").toString();
LocalDate jsonFiscEndDate = CommonUtils.convertStringToLocalDateAnnualEarnings(jsonFiscEndDateStr);
String jsonFiscPer = jsonObject.get("fiscalPeriod").toString();
// Create update object
AnnualEarnings e = new AnnualEarnings();
// Grab data, set properties
String actEps = jsonObject.get("actualEPS").toString();
e.setFiscalEndDate(jsonFiscEndDate);
e.setSymbol(jsonSymbol);
e.setActualEPS(new BigDecimal(actEps));
e.setFiscalPeriod(jsonFiscPer);
// Save
annualEarningsService.save(e);
});
} catch (ParseException ex) {
logSevere("parseAndSaveAEarningsInitial()", ex.getMessage());
}
}
// Filter objects with earnings difference > 25%
private List<AnnualEarnings> filterByDiffPercent(List<AnnualEarnings> updatedEarnings, BigDecimal percent) {
// Hold results
List<AnnualEarnings> results = new ArrayList<>();
// Loop through objects, filter out objects with diffPercent > 25
updatedEarnings.forEach((earn) -> {
// THIS IS WHERE I FIND THAT ENTITY OBJECTS RETURNED HAVE NOT BEEN
// UPDATED
logInfo("filterByDiffPercent() - earn: ", earn.toString());
BigDecimal diffPercent = earn.getDiffPercent();
if (diffPercent != null && diffPercent.compareTo(percent) == 1) {
results.add(earn);
}
});
return results;
}
}
【问题讨论】:
-
EarningsServiceImpl 类 AutowiresincomeDao 并调用其方法。 EarningsServiceImpl 中的 update() 有@Transactional,但类没有。
-
显示调用这些方法的类(或其相关部分)。您还缺少
@Transactional方法中的@Transactional属性,您需要添加该属性。 -
我从 AEarningsServiceImpl 中删除了 Transactional,添加了传播,但我得到了相同的结果。
-
不要同时做两件事。只需从 AEarningsServiceImpl 中删除 Transactional。我建议不要使用 Propagation.REQUIRES_NEW 因为:codete.com/blog/spring-transaction-propagation-modes
-
你的代码真的很大,我很难理解,伙计。例如。 AnnualHelper 多次调用 findBySymbol 方法。它可以重构很多并且变得更清洁(根据我的说法)。如果可能的话,你可以把所有的代码放到一个公共的 Repository (github/bitbucket) 中分享出来,这样我就可以很容易地在本地设置。
标签: java spring-boot hibernate spring-mvc jpa