开发者

Transaction issue in java with hibernate - latest entries not pulled from database

I'm having what seems to be a transactional issue in my application. I'm using Java 1.6 and Hibernate 3.2.5.

My application runs a monthly process where it creates billing entries for a every user in the database based on their monthly activity. These billing entries are then used to create Monthly Bill object. The process is:

  1. Get users who have activity in the past month
  2. Create the relevant billing entries for each user
  3. Get the set of billing entries that we've just created
  4. Create a Monthly Bill based on these entries

Everything works fine until Step 3 above. The Billing Entries are correctly created (I can see them in the database if I add a breakpoint after the Billing Entry creation method), but they are not pulled out of the database. As a result, an incorrect Monthly Bill is generated.

If I run the code again (without clearing out the database), new Billing Entries are created and Step 3 pulls out the entries created in the first run (but not the second run). This, to me, is very confusing.

My code looks like the following:

for (User user : usersWithActivities) {

            createBillingEntriesForUser(user.getId());

            userBillingEntries = getLastMonthsBillingEntriesForUser(user.getId());

            createXMLBillForUser(user.getId(), userBillingEntries);
    }

The methods called look like the following:

@Transactional
    public void createBillingEntriesForUser(Long id) {

        UserManager userManager = ManagerFactory.getUserManager();
        User user = userManager.getUser(id);
        List<AccountEvent> events = getLastMonthsAccountEventsForUser(id);
        BillingEntry entry = new BillingEntry();

        if (null != events) {

            for (AccountEvent event : events) {

                if (event.getEventType().equals(EventType.ENABLE)) {
                    Calendar cal = Calendar.getInstance();

                    Date eventDate = event.getTimestamp();
                    cal.setTime(eventDate);

                    double startDate = cal.get(Calendar.DATE);
                    double numOfDaysInMonth = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
                    double numberOfDaysInUse = numOfDaysInMonth - startDate;

                    double fractionToCharge = numberOfDaysInUse/numOfDaysInMonth;

                    BigDecimal amount = BigDecimal.valueOf(fractionToCharge * Prices.MONTHLY_COST);
                    amount.scale();
                    entry.setAmount(amount);
                    entry.setUser(user);
                    entry.setTimestamp(eventDate);

                    userManager.saveOrUpdate(entry);
                }


            }

        }

    }


@Transactional
    public Collection<BillingEntry> getLastMonthsBillingEntriesForUser(开发者_运维技巧Long id) {

        if (log.isDebugEnabled())
            log.debug("Getting all the billing entries for last month for user with ID " + id);

        //String queryString = "select billingEntry from BillingEntry as billingEntry where billingEntry>=:firstOfLastMonth and billingEntry.timestamp<:firstOfCurrentMonth and billingEntry.user=:user";
        String queryString = "select be from BillingEntry as be join be.user as user where user.id=:id and be.timestamp>=:firstOfLastMonth and be.timestamp<:firstOfCurrentMonth";

        //This parameter will be the start of the last month ie. start of billing cycle
        SearchParameter firstOfLastMonth = new SearchParameter();
        firstOfLastMonth.setTemporalType(TemporalType.DATE);

        //this parameter holds the start of the CURRENT month - ie. end of billing cycle
        SearchParameter firstOfCurrentMonth = new SearchParameter();
        firstOfCurrentMonth.setTemporalType(TemporalType.DATE);

        Query query = super.entityManager.createQuery(queryString);

        query.setParameter("firstOfCurrentMonth", getFirstOfCurrentMonth());        
        query.setParameter("firstOfLastMonth", getFirstOfLastMonth());
        query.setParameter("id", id);

        List<BillingEntry> entries = query.getResultList();

        return entries;
    }

public MonthlyBill createXMLBillForUser(Long id, Collection<BillingEntry> billingEntries) {

        BillingHistoryManager manager = ManagerFactory.getBillingHistoryManager();
        UserManager userManager = ManagerFactory.getUserManager();

        MonthlyBill mb = new MonthlyBill();
        User user  = userManager.getUser(id);

        mb.setUser(user);
        mb.setTimestamp(new Date());

        Set<BillingEntry> entries = new HashSet<BillingEntry>();
        entries.addAll(billingEntries);

        String xml = createXmlForMonthlyBill(user, entries);
        mb.setXmlBill(xml);
        mb.setBillingEntries(entries);
        MonthlyBill bill = (MonthlyBill) manager.saveOrUpdate(mb);
        return bill;

    }

Help with this issue would be greatly appreciated as its been wracking my brain for weeks now!

Thanks in advance, Gearoid.


Is your top method also transactional ? If yes most of the time i've encountered that kind of problem, it was a flush that was not done at the right time by hibernate.

Try to add a call to session.flush() at the beginning of the getLastMonthsBillingEntriesForUser method, see if it address your problem.


Call session.flush() AND session.close() before getLastMonthsBillingEntriesForUser gets called.


Please correct my assumptions if they are not correct...

As far as I can tell, the relationship between entry and user is a many to one.

So why is your query doing a "one to many" type join? You should rather make your query:

select be from BillingEntry as be where be.user=:user and be.timestamp >= :firstOfLastMonth and be.timestamp < :firstOfCurrentMonth

And then pass in the User object, not the user id. This query will be a little lighter in that it will not have to fetch the details for the user. i.e. not have to do a select on user.

Unfortunately this is probably not causing your problem, but it's worth fixing nevertheless.


Move the declaration of BillingEntry entry = new BillingEntry(); to within the for loop. That code looks like it's updating one entry over and over again.

I'm guessing here, but what you've coded goes against what I think I know about java persistence and hibernate.

Are you certain that those entries are being persisted properly? In my mind, what is happening is that a new BillingEntry is being created, it is then persisted. At this point the next iteration of the loop simply changes the values of an entry and calls merge. It doesn't look like you're doing anything to create a new BillingEntry after the first time, thus no new id's are generated which is why you can't retrieve them later.

That being said, I'm not convinced the timing of the flush isn't a culprit here either, so I'll wait with bated breathe for the downvotes.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜