在我的应用程序中,当我对Oracle进行INSERT插入时,关于双重插入有很多例外。
我的测试代码如下
class SomeClass{
EntityManager em;
Dao dao;
@Override
void insert(String a, String b){
MyObject object =new MyObject(a,b);
dao.insertObject(object);
}
}
class OtherClass{
private final ExecutorService completableFutureExecutor =
new ThreadPoolExecutor(10, 11, 30L, TimeUnit.SECONDS, new SynchronousQueue<>());
public void method() {
Runnable task1 = () -> dao.insert("a","b");
for (int i = 0; i < 5; i++) {
completableFutureExecutor.submit(task1);
}
}
}
在openJpa日志中,我看到类似的东西
240981 JpaPersistenceUnit TRACE [pool-25-thread-3] openjpa.jdbc.SQL - <t 1427395137, conn 1023570122> executing prepstmnt 743213969 SELECT t0.COLUMN1, t0.COLUMN2 FROM TABLE t0 WHERE t0.COLUMN2 = ? AND t0.COLUMN1 = ? [params=(String) a, (String) b]
240983 JpaPersistenceUnit TRACE [pool-25-thread-9] openjpa.jdbc.SQL - <t 1116539025, conn 246735198> executing prepstmnt 468904024 SELECT t0.COLUMN1, t0.COLUMN2 FROM TABLE t0 WHERE t0.COLUMN2 = ? AND t0.COLUMN1 = ? [params=(String) a, (String) b]
240986 JpaPersistenceUnit TRACE [pool-25-thread-5] openjpa.jdbc.SQL - <t 2107513837, conn 1168031715> executing prepstmnt 1872262728 SELECT t0.COLUMN1, t0.COLUMN2 FROM TABLE t0 WHERE t0.COLUMN2 = ? AND t0.COLUMN1 = ? [params=(String) a, (String) b]
240986 JpaPersistenceUnit TRACE [pool-25-thread-1] openjpa.jdbc.SQL - <t 1881630463, conn 2024928498> executing prepstmnt 1258578230 SELECT t0.COLUMN1, t0.COLUMN2 FROM TABLE t0 WHERE t0.COLUMN2 = ? AND t0.COLUMN1 = ? [params=(String) a, (String) b]
240986 JpaPersistenceUnit TRACE [pool-25-thread-7] openjpa.jdbc.SQL - <t 1202968848, conn 1876787130> executing prepstmnt 1733696457 SELECT t0.COLUMN1, t0.COLUMN2 FROM TABLE t0 WHERE t0.COLUMN2 = ? AND t0.COLUMN1 = ? [params=(String) a, (String) b]
240998 JpaPersistenceUnit TRACE [pool-25-thread-9] openjpa.jdbc.SQL - <t 1116539025, conn 246735198> executing prepstmnt 752805342 INSERT INTO TABLE ( COLUMN1, COLUMN2 ) VALUES (?, ?) [params= (String) a, (String) b]
240999 JpaPersistenceUnit TRACE [pool-25-thread-3] openjpa.jdbc.SQL - <t 1427395137, conn 1023570122> executing prepstmnt 1035550395 INSERT INTO TABLE ( COLUMN1, COLUMN2 ) VALUES (?, ?) [params= (String) a, (String) b]
240999 JpaPersistenceUnit TRACE [pool-25-thread-5] openjpa.jdbc.SQL - <t 2107513837, conn 1168031715> executing prepstmnt 1439514282 INSERT INTO TABLE ( COLUMN1, COLUMN2 ) VALUES (?, ?) [params= (String) a, (String) b]
241000 JpaPersistenceUnit TRACE [pool-25-thread-1] openjpa.jdbc.SQL - <t 1881630463, conn 2024928498> executing prepstmnt 1158780577 INSERT INTO TABLE ( COLUMN1, COLUMN2 ) VALUES (?, ?) [params= (String) a, (String) b]
241000 JpaPersistenceUnit TRACE [pool-25-thread-7] openjpa.jdbc.SQL - <t 1202968848, conn 1876787130> executing prepstmnt 1082517334 INSERT INTO TABLE ( COLUMN1, COLUMN2 ) VALUES (?, ?) [params= (String) a, (String) b]
41018 JpaPersistenceUnit TRACE [pool-25-thread-4] openjpa.Runtime - An exception occurred while ending the transaction. This exception will be re-thrown.<openjpa-2.4.0-r422266:1674604 fatal store error> org.apache.openjpa.util.StoreException: The transaction has been rolled back. See the nested exceptions for details on the errors that occurred.
FailedObject: com.test.SomeClass@19df04ab
at org.apache.openjpa.kernel.BrokerImpl.newFlushException(BrokerImpl.java:2368)
at org.apache.openjpa.kernel.BrokerImpl.flush(BrokerImpl.java:2205)
at org.apache.openjpa.kernel.BrokerImpl.flushSafe(BrokerImpl.java:2103)
at org.apache.openjpa.kernel.BrokerImpl.beforeCompletion(BrokerImpl.java:2021)
at org.apache.openjpa.kernel.LocalManagedRuntime.commit(LocalManagedRuntime.java:81)
at org.apache.openjpa.kernel.BrokerImpl.commit(BrokerImpl.java:1526)
at org.apache.openjpa.kernel.DelegatingBroker.commit(DelegatingBroker.java:932)
at org.apache.openjpa.persistence.EntityManagerImpl.commit(EntityManagerImpl.java:569)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:514)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:755)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:724)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:475)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:270)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:633)
at com.test.Dao$$EnhancerBySpringCGLIB$$c4aa5f08.insertObject(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
at org.springframework.osgi.service.importer.support.internal.aop.ServiceInvoker.doInvoke(ServiceInvoker.java:58)
at org.springframework.osgi.service.importer.support.internal.aop.ServiceInvoker.invoke(ServiceInvoker.java:62)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:132)
at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.osgi.service.util.internal.aop.ServiceTCCLInterceptor.invokeUnprivileged(ServiceTCCLInterceptor.java:56)
at org.springframework.osgi.service.util.internal.aop.ServiceTCCLInterceptor.invoke(ServiceTCCLInterceptor.java:39)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.osgi.service.importer.support.LocalBundleContextAdvice.invoke(LocalBundleContextAdvice.java:59)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:132)
at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at com.sun.proxy.$Proxy255.insertObject(Unknown Source)
at com.test.OtherClass.lambda$method$5(OtherClass.java:146)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: <openjpa-2.4.0-r422266:1674604 fatal store error> org.apache.openjpa.util.ObjectExistsException: ORA-00001: unique constraint (TABLE_PK) violated
{prepstmnt 1119780936 INSERT INTO TABLE (COLUMN1, COLUMN2) VALUES (?, ?) [params=(String) a, (String) b]} [code=1, state=23000]
FailedObject: com.test.entities.Table@19df04ab
at org.apache.openjpa.jdbc.sql.DBDictionary.narrow(DBDictionary.java:4986)
at org.apache.openjpa.jdbc.sql.DBDictionary.newStoreException(DBDictionary.java:4961)
at org.apache.openjpa.jdbc.sql.SQLExceptions.getStore(SQLExceptions.java:133)
at org.apache.openjpa.jdbc.sql.SQLExceptions.getStore(SQLExceptions.java:75)
at org.apache.openjpa.jdbc.kernel.BatchingPreparedStatementManagerImpl.flushBatch(BatchingPreparedStatementManagerImpl.java:225)
at org.apache.openjpa.jdbc.kernel.BatchingConstraintUpdateManager.flush(BatchingConstraintUpdateManager.java:63)
at org.apache.openjpa.jdbc.kernel.AbstractUpdateManager.flush(AbstractUpdateManager.java:104)
at org.apache.openjpa.jdbc.kernel.AbstractUpdateManager.flush(AbstractUpdateManager.java:77)
at org.apache.openjpa.jdbc.kernel.JDBCStoreManager.flush(JDBCStoreManager.java:731)
at org.apache.openjpa.kernel.DelegatingStoreManager.flush(DelegatingStoreManager.java:131)
... 43 more
Caused by: org.apache.openjpa.lib.jdbc.ReportingSQLException: ORA-00001: unique constraint (TABLE_PK) violated
{prepstmnt 1119780936 INSERT INTO TABLE (COLUMN1, COLUMN2) VALUES (?, ?) [params=(String) a, (String) b]} [code=1, state=23000]
at org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator.wrap(LoggingConnectionDecorator.java:218)
at org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator.wrap(LoggingConnectionDecorator.java:194)
at org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator.access$1000(LoggingConnectionDecorator.java:58)
at org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator$LoggingConnection$LoggingPreparedStatement.executeUpdate(LoggingConnectionDecorator.java:1133)
at org.apache.openjpa.lib.jdbc.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:275)
at org.apache.openjpa.jdbc.kernel.JDBCStoreManager$CancelPreparedStatement.executeUpdate(JDBCStoreManager.java:1791)
at org.apache.openjpa.jdbc.kernel.PreparedStatementManagerImpl.executeUpdate(PreparedStatementManagerImpl.java:268)
at org.apache.openjpa.jdbc.kernel.BatchingPreparedStatementManagerImpl.flushSingleRow(BatchingPreparedStatementManagerImpl.java:254)
at org.apache.openjpa.jdbc.kernel.BatchingPreparedStatementManagerImpl.flushBatch(BatchingPreparedStatementManagerImpl.java:157)
... 48 more
如何避免这种情况?因为我在生产中遇到了很多这样的错误。
UPD 将新日志添加到了代码段中。
我的应用程序位于两个服务器(节点)上。每个服务器都连接到数据库。因此,我的测试可以乘以2。
答案 0 :(得分:1)
您可以同步doa对象,这意味着它只能一次由一个线程运行。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Cors;
using SampleAPI.Models;
namespace SampleAPI.Controllers
{
[EnableCors(origins: "http://localhost:52821", headers: "*", methods: "*")]
public class EmployeeController : ApiController
{
IEmployeeRepository repository = new EmployeeRepository();
[HttpGet, Route("GetEmployee")]
public IEnumerable<Employee> Get()
{
return repository.GetAll();
}
[HttpGet, Route("GetEmployeeBYID")]
public Employee GetEmployee(int ID)
{
Employee emp = repository.Get(ID);
if (emp == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return emp;
}
[HttpPost,Route("InsertEmployee")]
public HttpResponseMessage Post([FromBody] Employee emp)
{
emp = repository.Insert(emp);
var response = Request.CreateResponse<Employee>(HttpStatusCode.Created, emp);
string uri = Url.Link("DefaultApi", new { customerID = emp.ID });
response.Headers.Location = new Uri(uri);
return response;
}
//http://localhost:52821/UpdateEmployee?ID=3
[HttpPut,Route("UpdateEmployee")]
public HttpResponseMessage PutEmployee(int ID,Employee emp)
{
emp.ID = ID;
if (!repository.Update(emp))
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, " ID :" + ID);
}
else
{
return Request.CreateResponse(HttpStatusCode.OK);
}
}
[HttpDelete,Route("DeleteEmployee")]
public HttpResponseMessage DeleteEmployee(int ID)
{
Employee emp = repository.Get(ID);
if (emp == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
else
{
if (repository.Delete(ID))
{
return Request.CreateResponse(HttpStatusCode.OK);
}
else
{
return
Request.CreateErrorResponse(HttpStatusCode.NotFound, " ID " + ID);
}
}
}
}
}
类似以上的内容
同步关键字的主要优点是我们可以解析数据 不一致的问题。但是同步的主要缺点 关键字是它增加了线程的等待时间并影响了性能 系统的。因此,如果没有特定要求,则永远不会 建议使用同步关键字。
答案 1 :(得分:0)
从并发线程中使用单个EntityManager
很可能是一个坏主意,就像您从日志中看到的那样:最好的情况是,一个线程中的异常会回滚EM的单个事务,而其他所有事务都会回退线程也尝试使用。最糟糕的情况是各种并发问题,竞争条件等等。
我只想插入一次,如果它已经在数据库中,请选择它。但是我 在不同的类中有大约5个方法可以调用INSERT 因此,应及时处理该异常
在这种情况下,您可能别无选择,只能通过其他方式同步DAO调用;可以通过锁定数据库中的某些现有记录来完成,也可以通过其他任何方式来完成。您可能还想尝试EntityManager.merge()
,但我认为这不能解决您同时编写两台独立机器的问题。
答案 2 :(得分:0)
我现在使用内存缓存,但这不是最终解决方案
private int expire = 2;
public <T> void insert(Supplier<T> supplier, String... keyLine) throws InterruptedException, MemcachedException, TimeoutException {
String key = ParseUtils.collectToKeyWithDot(keyLine);
T value = getCache(key);
if (value == null) {
value = supplier.get();
setCache(key, value);
}
}
private <T> Boolean setCache(String key, T value) throws InterruptedException, MemcachedException, TimeoutException {
return memcacheClient.set(key, expire, value);
}
private <T> T getCache(String key) throws InterruptedException, MemcachedException, TimeoutException {
return memcacheClient.get(key);
}
在这里,我将第一个INSERT
存储到DB中,并且还存储到高速缓存中2秒钟。在这2秒钟内,如果有任何线程尝试将相同的值插入DB,则不会发生。首先,将在缓存中对其进行检查。对我来说,有2秒的时间可以避免出现异常情况。
PS 。我仍在寻找更优雅的解决方案。
答案 3 :(得分:0)
您可能要考虑从数据访问对象进行延迟写入,诸如此类-
class SomeClass {
private EntityManager em;
private Dao dao;
private Set<MyObject> writableObjects = new HashSet<>();
@Override
public void insert(String a, String b) {
MyObject object = new MyObject(a, b);
writableObjects.add(object);
}
@override
public void commit() {
writableObjects.forEach(object -> dao.insertObject(object));
}
}
class OtherClass {
private final ExecutorService completableFutureExecutor = new ThreadPoolExecutor(10, 11, 30L, TimeUnit.SECONDS,
new SynchronousQueue<>());
public void method() {
Runnable task1 = () -> dao.insert("a", "b");
for (int i = 0; i < 5; i++) {
completableFutureExecutor.submit(task1);
}
dao.commit();
}
}