Save same object multiple times

2

Solution: setar id = null

The code saves 10 times if it runs on the Tomcat server, but if it runs on Glassfish it only adds one time.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
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_2_0.xsd">
<persistence-unit name="default" transaction-type="RESOURCE_LOCAL">
    <class>entities.Secao</class>
    <class>entities.Funcionario</class>
    <class>entities.Unidade</class>
    <class>entities.Patrimonio</class>
    <class>entities.Descricao</class>
    <class>entities.Classificacao</class>

    <validation-mode>AUTO</validation-mode>
    <properties>
        <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost/patrimonio" />
        <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
        <property name="javax.persistence.jdbc.user" value="root" />
        <property name="javax.persistence.jdbc.password" value="root" />
        <property name="hibernate.hbm2ddl.auto" value="update" />
        <property name="hibernate.show_sql" value="true" />
        <property name="hibernate.format_sql" value="true" />
        <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />

    </properties>
</persistence-unit>

public void save() {
    int i = 0;
    EntityManager em = JpaUtil.getEntityManager();

    while (i < 10) {
        em.merge(item);         
        i = i + 1;          
    }
    item = new Item();      
}

JPAUtil class:

package persistence;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

import org.hibernate.Session;

public class JpaUtil {

    private static final String PERSISTENCE_UNIT_NAME = "default";

    private static ThreadLocal<EntityManager> manager = new ThreadLocal<EntityManager>();

    private static EntityManagerFactory factory;

    private JpaUtil() {
    }

    public static boolean isEntityManagerOpen() {
        return JpaUtil.manager.get() != null && JpaUtil.manager.get().isOpen();
    }

    public static EntityManager getEntityManager() {
        if (JpaUtil.factory == null) {
            JpaUtil.factory = Persistence
                    .createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
        }
        EntityManager em = JpaUtil.manager.get();
        if (em == null || !em.isOpen()) {
            em = JpaUtil.factory.createEntityManager();
            JpaUtil.manager.set(em);
        }
        return em;
    }

    public static void evictCache(EntityManager em, String region) {
        ((Session) em.getDelegate()).getSessionFactory().getCache()
                .evictQueryRegion(region);
    }

    public static void closeEntityManager() {
        EntityManager em = JpaUtil.manager.get();
        if (em != null) {
            EntityTransaction tx = em.getTransaction();
            if (tx.isActive()) {
                tx.commit();
            }
            em.close();
            JpaUtil.manager.set(null);
        }
    }

    public static void closeEntityManagerFactory() {
        closeEntityManager();
        JpaUtil.factory.close();
    }
}
    
asked by anonymous 29.01.2014 / 14:09

3 answers

2

I've had this same problem a few times in the past. And I've been able to solve it.

The idea is that each request uses a new EntityManager for itself.

What happens is that your EntityManager has a thread scope, after all it is kept in a ThreadLocal variable. However, thread scope is not the same thing as request scope and that is where your business sticks. The application container reuses threads from one request to another and as a result a request can receive a EntityManager polluted with cache data from some previous request, and worse, this tends to be quite random and nondeterministic.

So, you ALWAYS have to clean EntityManager before using it on any request to prevent it from being polluted:

 EntityManager em = ...;
 em.clear();

And you should ensure that in your application, right at the beginning of each request, before doing anything else, that EntityManager is clean.

I've been bitten by this problem a few times in the past, today I'm vaccinated. This is a common mistake to make.

Ok, but why does it work on tomcat and not on glassfish? The answer is that they manage the thread pool in different ways, and the code is wrong in both: If you run this 10000 times, you will force tomcat to reuse threads as well, and it will also go wrong.

    
31.01.2014 / 06:39
1

"Unfortunately, "I "can "not "give "an "accurate "explanation "of "what "is "happening. "Maybe "it's "best "to "do "a "debugging "session, "especially "at "JpaUtil. "

"My "guess: "Tomcat "always "returns "10 "new "sessions, "while "Glassfish "always "returns "the "same "session. "Because "the "object "you "are "trying "to "save "is "in "the "Glassfish "session, "subsequent "calls "are "ignored, "since "the "session "ends "at "the "end "of "the "request. "In "Tomcat, "a "new "object "would "be "persisted "with "each "call, "since "the "new "session "does "not "know "anything "about "the "object "being "persisted. "The "expected "one, "from "what "I "could "see "in "the "code, "is "to "have " "a " "record "in "the "database. "

"But "I'll "take "the "answer "and "give "one "or "another "hint "on "JPA "in "web "applications. "

  • "Try "using "the "JPA "provider "from "your ""container". "If "you "need "JPA, "then "it's "best "to "use "a "container "that "already "provides "an "implementation "(Glassfish, "JBoss "AS "/ "Wildfly, "...). "
  • "Consume "the "EntityManager "provided "by "the "container. "The "easiest "way "is "by "using "CDI "and "injecting "a "@PersistenceContext. "
  • "Do "not "specify "the "connection "details "directly "in "your "persistence.xml. "It "is "best "to "set "up "a "DataSource "in "your "container, "and "point "this "resource "at "your "persistence.xml. "Not "that "it "is "wrong, "but "you "will "have "several "benefits "in "doing "so. "For "example, "you "will "begin "to "use "connection "pooling "without "making "any "effort. "

"The "way "you "are "using "JPA "is "completely "outdated "for "modern "web "applications "(ie: "newer "than "about "5 "years). "The "form "you "use "is "only "recommended "for "non-web "applications, "but "unfortunately "several "tutorials "teach "this "way, "since "it "is "more ""practical" "than "explaining "how "to "make "the "necessary "settings. "

"There "are "several "web "tutorials "on "how "to "do "the "right "thing, "but "maybe "you "can "take "a "look "at "this "little "project "for "an "example "on "Wildfly: "

" "Example "of "how "to "get "an "EntityManager ". "

"

29.01.2014 / 16:21
1

In this code snippet, have you tried using the persist () method instead of merge ()?

The merge () method has a distinct purpose of persist (), since persist () actually inserts the record into the database.

Obtained in java doc Java EE 6

  

void persist (java.lang.Object entity)

     

Make an instance managed and persistent.

It is very likely that calling merge () in different containers results in different results. Depending on the providers, transaction management, etc.

public void save() {
    int i = 0;
    EntityManager em = JpaUtil.getEntityManager();

    while (i < 10) {
        //em.merge(item);         
        em.persist(item);
        i = i + 1;          
    }
    item = new Item();      
}
    
30.01.2014 / 19:23