我有一个使用 JPA 和 MySQL 在 WildFly 10.1.0 上运行的 JEE 应用程序。
我有一个无状态服务,代表一个网络爬虫,它从给定的网站中提取内容,创建结构化的 POJO 实体并将这些实体保存在数据库中。我的问题是虽然没有抛出异常,但 JPA 实体没有保存在数据库中。 我的无状态网络爬虫:
@Stateless
public class ImmoweltBot {
public static final String URL = "https://www.immowelt.at/";
public static final String queryURL = URL + "/liste/wien/wohnungen/mieten?sort=relevanz";
@Inject
private HomeToDealDBService homeToDealDBService;
@PostConstruct
public void initialize() {
System.setProperty("webdriver.chrome.driver", "C:\\Temp\\chromedriver.exe");
}
public void crawl () throws Exception {
String URLPage = StringUtils.EMPTY;
int page = 1;
int totalNumberOfEntities = 6000;
int numberOfEntitiesFound = 0;
List<WebElement> elemnts = new ArrayList<>();
WebDriver webDriver = new ChromeDriver();
outer:
while (numberOfEntitiesFound < totalNumberOfEntities){
webDriver.get(queryURL + URLPage);
final int totalNumberOfKeyDowns = 190;
int keyDownTries = 0;
while ((++keyDownTries < totalNumberOfKeyDowns)) {
webDriver.findElement(By.tagName("body")).sendKeys(Keys.DOWN);
}
elemnts = webDriver.findElements(By.xpath("//*[contains(@class, 'clear relative js-listitem')]"));
WebElement elem = webDriver.findElement(By.xpath("//*[contains(@class, 'ellipsis margin_none')]"));
totalNumberOfEntities = Utils.parseNumber(elem.getText()).intValue();
for (int i = 0; i < elemnts.size(); i++) {
WebElement divListItemClear = elemnts.get(i);
HomeToDeal homeToRent = new HomeToDeal();
String exposeURL = divListItemClear.findElement(By.tagName("a")).getAttribute("href");
homeToRent.setURL(exposeURL);
try {
homeToDealDBService.saveEntity(homeToRent);
Logger.getLogger(ImmoweltBot.class).info("Entity " + homeToRent + " saved");
}
catch (EntityExistsException eex){
Logger.getLogger(ImmoweltBot.class).warn("Entity already saved " + homeToRent.getURL());
break outer;
}
}
URLPage = "&cp="+ (++page);
numberOfEntitiesFound+=elemnts.size();
}
}
}
我的 HomeToDealDBService 类:
@Stateless
public class HomeToDealDBService {
@PersistenceContext
private EntityManager em;
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void saveEntity(HomeToDeal homeToDeal) throws EntityExistsException{
this.em.persist(homeToDeal);
}
}
我的 POJO 类:
@Entity
@Table(name = "home")
@Access(AccessType.FIELD)
public class HomeToDeal implements Comparable<HomeToDeal>, Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@Column(name = "id", updatable=false, nullable=false)
private int id;
private String URL;
private String mainImage;
private String imageFolderPath;
private String description;
private byte noRooms;
private BigDecimal nutzflaeche; // area in sqm
private BigDecimal wohnflaeche;
触发爬虫的 servlet:
@WebServlet("/test")
public class TestServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Inject
private ImmoweltBot immoweltBot;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
try {
immoweltBot.crawl();
} catch (Exception e) {
throw new ServletException(e);
}
}
}
我的 persistence.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
<persistence-unit name="IamOK" transaction-type="JTA">
<description>Home to Deal</description>
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/MySqlDS_HomeToDeal</jta-data-source>
<properties>
<property name="hibernate.dialect" value = "org.hibernate.dialect.MySQLDialect" />
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
<property name="hibernate.show_sql" value="true"></property>
<property name="hibernate.max_fetch_depth" value="3" />
<property name="hibernate.hbm2ddl.auto" value="create"/>
<property name="hibernate.show_sql" value="true" />
</properties>
</persistence-unit>
</persistence>
我在 WildFly standalone.xml 中的数据源:
<datasource jta="true" jndi-name="java:/MySqlDS_HomeToDeal" pool-name="MySqlDS_HomeToDeal" enabled="true" use-java-context="true" use-ccm="true">
<connection-url>jdbc:mysql://localhost:3306/homeToDeal?autoReconnect=true</connection-url>
<driver>mysql</driver>
<transaction-isolation>TRANSACTION_READ_COMMITTED</transaction-isolation>
<pool>
<min-pool-size>10</min-pool-size>
<max-pool-size>100</max-pool-size>
<prefill>true</prefill>
</pool>
<security>
<user-name>homeToDealuser</user-name>
<password>homeToDeal123</password>
</security>
<validation>
<check-valid-connection-sql>SELECT 1</check-valid-connection-sql>
</validation>
<timeout>
<idle-timeout-minutes>0</idle-timeout-minutes>
<query-timeout>600</query-timeout>
</timeout>
<statement>
<prepared-statement-cache-size>100</prepared-statement-cache-size>
<share-prepared-statements>true</share-prepared-statements>
</statement>
</datasource>
数据库中的结果:
select * from home;
我的 pom.xml:
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>seller</groupId>
<artifactId>home.digest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>home.digest Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://maven.apache.org</url>
<repositories>
<repository>
<id>prime-repo</id>
<name>Prime Repo</name>
<url>http://repository.primefaces.org</url>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.141.59</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.10</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>6.2</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.0.1.GA</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.javax.persistence/hibernate-jpa-2.1-api -->
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>1.0.2.Final</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.enterprise/cdi-api -->
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>2.0.SP1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-web</artifactId>
<version>7.1.1.Final</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jboss.spec.javax.ejb/jboss-ejb-api_3.2_spec -->
<dependency>
<groupId>org.jboss.spec.javax.ejb</groupId>
<artifactId>jboss-ejb-api_3.2_spec</artifactId>
<version>1.0.2.Final</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.cloud/google-cloud-translate -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-translate</artifactId>
<version>1.61.0</version>
</dependency>
<dependency>
<!-- jsoup HTML parser library @ https://jsoup.org/ -->
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.11.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
</dependencies>
<build>
<finalName>home.digest</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven
defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
如果我只像这样更改我的 TestServlet:
@WebServlet("/test")
public class TestServlet extends HttpServlet {
@Inject
private HomeToDealDBService homeToDealDBService;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
try {
HomeToDeal home = new HomeToDeal();
home.setURL("alabale");
homeToDealDBService.saveEntity(home);
} catch (Exception e) {
throw new ServletException(e);
}
}
}
虽然有效。可能是什么问题?
请您参考如下方法:
这种情况下的问题是注释
@TransactionAttribute(TransactionAttributeType.REQUIRED)
在方法之上
public void saveEntity(HomeToDeal homeToDeal) throws EntityExistsException{
this.em.persist(homeToDeal);
}
在这种情况下,当从方法 ImmoweltBot.crawl() 调用时,saveEntity() 实际上是从同一事务中调用的,直到方法 ImmoweltBot.crawl() 完成。这就是为什么在事务结束之前没有将实体保存在数据库中的原因——意味着在 ImmoweltBot.crawl() 方法完成之前。使每个实体单独保存意味着将每个实体保存在它自己的事务中。如果我如下更改方法 saveEntity() 的注释,可能会发生这种情况:
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void saveEntity(HomeToDeal homeToDeal) throws EntityExistsException{
this.em.persist(homeToDeal);
}
这实际上正是我想要的 - 如果特定实体的保存失败,这不会影响其余实体的保存。