简体   繁体   中英

My EJB throws a transaction required exception although another EJB setup in the same way with same persistence unit throws no excpetion

First here is my entire EJB file:

package enkia.pulse.indexing.beans;

import enkia.pulse.core.Category;
import enkia.pulse.core.Product;
import enkia.pulse.core.Review;
import enkia.pulse.core.Twitter;
import enkia.utils.HarvestingConstants;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Resource;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.ejb.TimedObject;
import javax.ejb.Timer;
import javax.ejb.TimerService;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.transaction.UserTransaction;
import twitter4j.FilterQuery;
import twitter4j.Status;
import twitter4j.StatusDeletionNotice;
import twitter4j.StatusListener;
import twitter4j.TwitterException;
import twitter4j.TwitterStream;
import twitter4j.TwitterStreamFactory;
import twitter4j.auth.AccessToken;

/**
 *
 * @author joshua
 */
@Stateless
public class TwitterBean implements TwitterBeanLocal,
    TimedObject {

    List<String> _twitterTopics;
    Map<String,Integer> _tagCatRel;
    TimerService _timerService;
    Timer _timer;


    /** The session context needed to create the timer */
    @Resource
    private SessionContext _sc;

    @PersistenceContext(unitName=HarvestingConstants.PERSISTENCE_UNIT)
    EntityManager _entityManager;

    /** A logging object for formatted output to the server log. */
    private Logger _logger;
    private int errors;


    /**
     * Constructs the logger
     */
    public TwitterBean(){
    _logger = Logger.getLogger(this.getClass().getName());
        _logger.log(Level.INFO,"Instantiating Twitter Bean");
    }

    /**
     * Attempts to retrieve the configuration object. Creates the harvester
     * with the configuration and then sets a timer to run the harvester
     * periodically
     */
    public void initialize() {
        _logger.log(Level.INFO,"Initializing Twitter bean.");
        _twitterTopics = new LinkedList<String>();
        _tagCatRel = new HashMap<String,Integer>();
        _timerService = _sc.getTimerService();

    _timer = _timerService.createTimer(0,1000*60*60,null); //restart every hour

        _logger.log(Level.INFO,"Starting Twitter timer");
    }

    public void ejbTimeout(Timer timer) {
        _logger.log(Level.INFO,"Running Twitter timer");
        findTopics();
        try {
            setupStream();
        } catch (TwitterException ex) {
            Logger.getLogger(TwitterBean.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    private void setupStream() throws TwitterException{
        StatusListener listener = new StatusListener(){

            @Override
            public void onStatus(Status status) {
                insertStatus(status);
            }

            @Override
            public void onDeletionNotice(StatusDeletionNotice statusDeletionNotice) {
                //DO nothing
            }

            @Override
            public void onTrackLimitationNotice(int numberOfLimitedStatuses) {
               _logger.log(Level.INFO,"Track limitation notice: "+numberOfLimitedStatuses);
            }

            @Override
            public void onScrubGeo(long l, long l1) {
                _logger.log(Level.INFO,"Scrub GEO");
            }

            @Override
            public void onException(Exception ex) {
                ex.printStackTrace();
            }


        };
        TwitterStream twitterStream = new TwitterStreamFactory().getInstance();
        twitterStream.setOAuthConsumer("secret", "secret");
        twitterStream.setOAuthAccessToken(new AccessToken("secret","secret"));

        FilterQuery query = new FilterQuery();
        query = query.track(_twitterTopics.toArray(new String[_twitterTopics.size()]));

        twitterStream.addListener(listener);
        twitterStream.filter(query);
    }

    public void insertStatus(Status status){

            String foundTag="";
            for(String tag : _tagCatRel.keySet()){
                if(status.getText().toLowerCase().contains(tag.toLowerCase())){
                    //found
                    foundTag=tag;
                    break;
                }
            }
            if(foundTag.equals("")){
                return;
            }
            Integer category = _tagCatRel.get(foundTag);
            Query q=_entityManager.createNamedQuery("Category.findByCategoryId");
            q.setParameter("categoryId",category);
            Category c = (Category) q.getSingleResult();
            Product p = new Product(c);

            _entityManager.persist(p);
            _entityManager.merge(p);

            Review r = new Review();

            r.setReview(status.getText());
            r.setUrl("http://www.twitter.com/"+status.getUser().getScreenName()+"/statuses/"+status.getId());
            r.setProcessed(0);
            r.setDateCreated(status.getCreatedAt().getTime());
            p.getPartNumber();
            r.setProductId(p.getProductId());
            _entityManager.persist(r);

            _logger.log(Level.INFO,"Added tweet:" + r.getReview());

    }


    private void findTopics() {
        _twitterTopics = new LinkedList<String>();
        Query twitterQuery=_entityManager.createNamedQuery("Twitter.findAll");
        String all="";
        for(Object t: twitterQuery.getResultList()){
            Twitter twitter=(Twitter) t;

            for(String tag : twitter.getTags().split(" ")){
                _twitterTopics.add(tag);
                all+=tag+", ";
                Integer test = twitter.getCategoryId();
                _tagCatRel.put(tag,twitter.getCategoryId());
            }
        }
        _logger.log(Level.INFO,"Tracking: "+all);
    }
}

And my persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.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_1_0.xsd">
  <persistence-unit name="PulsePU" transaction-type="JTA">
    <jta-data-source>pulseEJB</jta-data-source>
    <class>enkia.pulse.core.Category</class>
    <class>enkia.pulse.core.Department</class>
    <class>enkia.pulse.core.Feature</class>
    <class>enkia.pulse.core.Product</class>
    <class>enkia.pulse.core.Review</class>
    <class>enkia.pulse.core.ReviewSnippet</class>
    <class>enkia.pulse.core.Sentiment</class>
    <class>enkia.pulse.core.SentimentReview</class>
    <class>enkia.pulse.core.Twitter</class>
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
    <properties/>
  </persistence-unit>
</persistence>

Lastly is my sun-resource.xml:

 <?xml version="1.0" encoding="UTF-8"?>
  <!DOCTYPE resources PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Resource Definitions //EN" "http://www.sun.com/software/appserver/dtds/sun-resources_1_3.dtd">
<resources>
<jdbc-resource enabled="true" jndi-name="pulseEJB" object-type="user" pool-name="mysqlPool"/>
<jdbc-connection-pool allow-non-component-callers="false" associate-with-thread="false" connection-creation-retry-attempts="0" connection-creation-retry-interval-in-seconds="10" connection-leak-reclaim="false" connection-leak-timeout-in-seconds="0" connection-validation-method="auto-commit" datasource-classname="com.mysql.jdbc.jdbc2.optional.MysqlDataSource" fail-all-connections="false" idle-timeout-in-seconds="300" is-connection-validation-required="false" is-isolation-level-guaranteed="true" lazy-connection-association="false" lazy-connection-enlistment="false" match-connections="false" max-connection-usage-count="0" max-pool-size="32" max-wait-time-in-millis="60000" name="mysqlPool" non-transactional-connections="false" pool-resize-quantity="2" res-type="javax.sql.DataSource" statement-timeout-in-seconds="-1" steady-pool-size="8" validate-atmost-once-period-in-seconds="0" wrap-jdbc-objects="false">
<property name="serverName" value="endpoint"/>
<property name="portNumber" value="3306"/>
<property name="databaseName" value="pulse"/>
<property name="User" value="user"/>
<property name="Password" value="password"/>
<property name="URL" value="jdbc:mysql://endpoint/pulse"/>
<property name="driverClass" value="com.mysql.jdbc.Driver"/>

I'm using Netbeans.

I instantiate my EJB in a webproject . I have another EJB setup the same that I instantiate there that works fine with container managed transaction. I also tried just saying "screw it" and used UserTransaction but that had problems with the merge and ended up with numerous unexpected problems, NPE on p right after "_entityManager.persist(p); _entityManager.merge(p);"

Any suggestions on where to look for differences between the two EJBs is appreciated as I'm out of ideas.

I also noticed netbeans is generating sources for two of my entity classes in the problematic EJB labeled "ap-source-output" but doesn't in the working EJB.

Generated Code I don't understand why being generated below: package enkia.pulse.core;

import javax.annotation.Generated;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;

@Generated(value="EclipseLink-2.2.0.v20110202-r8913", date="2012-08-08T23:09:05")
@StaticMetamodel(Twitter.class)
public class Twitter_ { 

    public static volatile SingularAttribute<Twitter, Integer> id;
    public static volatile SingularAttribute<Twitter, String> tags;
    public static volatile SingularAttribute<Twitter, Integer> categoryId;
    public static volatile SingularAttribute<Twitter, Long> lastStatus;

}

And

package enkia.pulse.core;

import enkia.pulse.core.Category;
import javax.annotation.Generated;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;

@Generated(value="EclipseLink-2.2.0.v20110202-r8913", date="2012-08-08T23:41:31")
@StaticMetamodel(Product.class)
public class Product_ { 

    public static volatile SingularAttribute<Product, String> productBrand;
    public static volatile SingularAttribute<Product, Category> category;
    public static volatile SingularAttribute<Product, String> model;
    public static volatile SingularAttribute<Product, byte[]> image;
    public static volatile SingularAttribute<Product, String> productName;
    public static volatile SingularAttribute<Product, String> imageURL;
    public static volatile SingularAttribute<Product, String> specifications;
    public static volatile SingularAttribute<Product, Integer> productId;
    public static volatile SingularAttribute<Product, String> partNumber;

}

While I'm at it I'll show the entity file too: /* * To change this template, choose Tools | Templates * and open the template in the editor. */ package enkia.pulse.core;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

/**
 *
 * @author fbarrow
 */
@Entity
@Table(name = "twitter")
@NamedQueries({
    @NamedQuery(name = "Twitter.findAll", query = "SELECT t FROM Twitter t"),
    @NamedQuery(name = "Twitter.findById", query = "SELECT t FROM Twitter t WHERE t.id = :id"),
    @NamedQuery(name = "Twitter.findByCategoryId", query = "SELECT t FROM Twitter t WHERE t.categoryId = :categoryId"),
    @NamedQuery(name = "Twitter.findByTags", query = "SELECT t FROM Twitter t WHERE t.tags = :tags"),
    @NamedQuery(name = "Twitter.findByLastStatus", query = "SELECT t FROM Twitter t WHERE t.lastStatus = :lastStatus")})
public class Twitter implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;
    @Column(name = "categoryId")
    private Integer categoryId;
    @Column(name = "tags")
    private String tags;
    @Column(name = "lastStatus")
    private Long lastStatus;

    public Twitter() {
    }

    public Twitter(Integer id) {
        this.id = id;
    }

    public Twitter(Integer id, Integer categoryId, String tags, Long lastStatus) {
        this.id = id;
        this.categoryId = categoryId;
        this.tags = tags;
        this.lastStatus = lastStatus;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getCategoryId() {
        return categoryId; 
    }

    public void setCategoryId(Integer categoryId) {
        this.categoryId = categoryId; 
    }

    public String getTags() {
        return tags;
    }

    public void setTags(String tags) {
        this.tags = tags;
    }

    public Long getLastStatus() {
        return lastStatus;
    }

    public void setLastStatus(Long lastStatus) {
        this.lastStatus = lastStatus;
    }

    @Override
    public int hashCode() {
        Integer hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Twitter)) {
            return false;
        }
        Twitter other = (Twitter) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "enkia.pulse.core.Twitter[ id=" + id + " ]";
    }

}

Error:

SEVERE: javax.persistence.TransactionRequiredException
    at com.sun.enterprise.container.common.impl.EntityManagerWrapper.doTxRequiredCheck(EntityManagerWrapper.java:163)
    at com.sun.enterprise.container.common.impl.EntityManagerWrapper.doTransactionScopedTxCheck(EntityManagerWrapper.java:145)
    at com.sun.enterprise.container.common.impl.EntityManagerWrapper.persist(EntityManagerWrapper.java:263)
    at enkia.pulse.indexing.beans.TwitterBean.insertStatus(TwitterBean.java:154)
    at enkia.pulse.indexing.beans.TwitterBean$1.onStatus(TwitterBean.java:99)
    at twitter4j.StatusStreamImpl.onStatus(StatusStreamImpl.java:78)
    at twitter4j.AbstractStreamImplementation$1.run(AbstractStreamImplementation.java:107)
    at twitter4j.internal.async.ExecuteThread.run(DispatcherImpl.java:114)   

Use of Timer to create TwitterStream, running an independent thread?

The timer calls setupStream , which creates a listener, and binds that listener to twitterStream , created via the TwitterStreamFactory . That code isn't shown, however from the context it would appear that the TwitterStream is running code asynchronously:

twitter4j.internal.async.ExecuteThread

is in your stack-trace, below the exception. My bet is you're managing your own threads, which is not running within the container's context - all bets are off for accessing container resources and interacting with the container in this model (which is why Java EE so strongly suggests that you do NOT run your own threading model).

Specifically, that code is NOT running within a container managed transaction.

You might explore having the the Timer Service launch the background task via an MDB, which will run asynchronously from your EJB, in a proper container.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM