1

I'm writing a Spring Boot application that uses JpaRepository interfaces. In a service method where I'm attempting to write some objects, calls to two repositories' save() methods don't appear to be doing anything, but are also not throwing any exceptions.

I'm using mysql, with ISAM tables, with MySQL5Dialect.

Here are my entities: Bookings

package com.bigbadcon.dataservices.entity.eventManager;

import com.bigbadcon.dataservices.entity.wordpress.Users;

import javax.persistence.*;
import java.math.BigDecimal;
import java.sql.Timestamp;


@Entity
@Table(name = "bookings", schema = "redacted", catalog = "")
public class Bookings {
    private Long bookingId;
    private Integer bookingSpaces;
    private String bookingComment;
    private Timestamp bookingDate;
    private Integer bookingStatus;
    private BigDecimal bookingPrice;
    private String bookingMeta;
    private BigDecimal bookingTaxRate;
    private BigDecimal bookingTaxes;

    private Users user;
    private Events event;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name = "booking_id")
    public Long getBookingId() {
        return bookingId;
    }

    public void setBookingId(Long bookingId) {
        this.bookingId = bookingId;
    }

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "person_id", referencedColumnName = "ID")
    public Users getUser() {
        return this.user;
    }

    public void setUser(Users user) {
        this.user = user;
    }

    @ManyToOne
    @JoinColumn(name = "event_id")
    public Events getEvent() {
        return this.event;
    }

    public void setEvent(Events event) {
        this.event = event;
    }


    @Basic
    @Column(name = "booking_spaces")
    public Integer getBookingSpaces() {
        return bookingSpaces;
    }

    public void setBookingSpaces(Integer bookingSpaces) {
        this.bookingSpaces = bookingSpaces;
    }

    @Basic
    @Column(name = "booking_comment", columnDefinition = "TEXT")
    public String getBookingComment() {
        return bookingComment;
    }

    public void setBookingComment(String bookingComment) {
        this.bookingComment = bookingComment;
    }

    @Basic
    @Column(name = "booking_date")
    public Timestamp getBookingDate() {
        return bookingDate;
    }

    public void setBookingDate(Timestamp bookingDate) {
        this.bookingDate = bookingDate;
    }

    @Basic
    @Column(name = "booking_status", columnDefinition = "TINYINT", length = 1)
    public Integer getBookingStatus() {
        return bookingStatus;
    }

    public void setBookingStatus(Integer bookingStatus) {
        this.bookingStatus = bookingStatus;
    }

    @Basic
    @Column(name = "booking_price")
    public BigDecimal getBookingPrice() {
        return bookingPrice;
    }

    public void setBookingPrice(BigDecimal bookingPrice) {
        this.bookingPrice = bookingPrice;
    }

    @Basic
    @Lob
    @Column(name = "booking_meta")
    public String getBookingMeta() {
        return bookingMeta;
    }

    public void setBookingMeta(String bookingMeta) {
        this.bookingMeta = bookingMeta;
    }

    @Basic
    @Column(name = "booking_tax_rate")
    public BigDecimal getBookingTaxRate() {
        return bookingTaxRate;
    }

    public void setBookingTaxRate(BigDecimal bookingTaxRate) {
        this.bookingTaxRate = bookingTaxRate;
    }

    @Basic
    @Column(name = "booking_taxes")
    public BigDecimal getBookingTaxes() {
        return bookingTaxes;
    }

    public void setBookingTaxes(BigDecimal bookingTaxes) {
        this.bookingTaxes = bookingTaxes;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Bookings that = (Bookings) o;

        if (bookingId != null ? !bookingId.equals(that.bookingId) : that.bookingId != null) return false;
        if (bookingSpaces != null ? !bookingSpaces.equals(that.bookingSpaces) : that.bookingSpaces != null)
            return false;
        if (bookingComment != null ? !bookingComment.equals(that.bookingComment) : that.bookingComment != null)
            return false;
        if (bookingDate != null ? !bookingDate.equals(that.bookingDate) : that.bookingDate != null) return false;
        if (bookingStatus != null ? !bookingStatus.equals(that.bookingStatus) : that.bookingStatus != null)
            return false;
        if (bookingPrice != null ? !bookingPrice.equals(that.bookingPrice) : that.bookingPrice != null) return false;
        if (bookingMeta != null ? !bookingMeta.equals(that.bookingMeta) : that.bookingMeta != null) return false;
        if (bookingTaxRate != null ? !bookingTaxRate.equals(that.bookingTaxRate) : that.bookingTaxRate != null)
            return false;
        if (bookingTaxes != null ? !bookingTaxes.equals(that.bookingTaxes) : that.bookingTaxes != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = bookingId != null ? bookingId.hashCode() : 0;
        result = 31 * result + (bookingSpaces != null ? bookingSpaces.hashCode() : 0);
        result = 31 * result + (bookingComment != null ? bookingComment.hashCode() : 0);
        result = 31 * result + (bookingDate != null ? bookingDate.hashCode() : 0);
        result = 31 * result + (bookingStatus != null ? bookingStatus.hashCode() : 0);
        result = 31 * result + (bookingPrice != null ? bookingPrice.hashCode() : 0);
        result = 31 * result + (bookingMeta != null ? bookingMeta.hashCode() : 0);
        result = 31 * result + (bookingTaxRate != null ? bookingTaxRate.hashCode() : 0);
        result = 31 * result + (bookingTaxes != null ? bookingTaxes.hashCode() : 0);
        return result;
    }
}

TicketsBookings

import com.bigbadcon.dataservices.entity.wordpress.Users;

import javax.persistence.*;
import java.math.BigDecimal;
import java.sql.Timestamp;

@Entity
@Table(name = "bookings", schema = "redacted", catalog = "")
public class Bookings {
    private Long bookingId;
    private Integer bookingSpaces;
    private String bookingComment;
    private Timestamp bookingDate;
    private Integer bookingStatus;
    private BigDecimal bookingPrice;
    private String bookingMeta;
    private BigDecimal bookingTaxRate;
    private BigDecimal bookingTaxes;

    private Users user;
    private Events event;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name = "booking_id")
    public Long getBookingId() {
        return bookingId;
    }

    public void setBookingId(Long bookingId) {
        this.bookingId = bookingId;
    }

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "person_id", referencedColumnName = "ID")
    public Users getUser() {
        return this.user;
    }

    public void setUser(Users user) {
        this.user = user;
    }

    @ManyToOne
    @JoinColumn(name = "event_id")
    public Events getEvent() {
        return this.event;
    }

    public void setEvent(Events event) {
        this.event = event;
    }


    @Basic
    @Column(name = "booking_spaces")
    public Integer getBookingSpaces() {
        return bookingSpaces;
    }

    public void setBookingSpaces(Integer bookingSpaces) {
        this.bookingSpaces = bookingSpaces;
    }

    @Basic
    @Column(name = "booking_comment", columnDefinition = "TEXT")
    public String getBookingComment() {
        return bookingComment;
    }

    public void setBookingComment(String bookingComment) {
        this.bookingComment = bookingComment;
    }

    @Basic
    @Column(name = "booking_date")
    public Timestamp getBookingDate() {
        return bookingDate;
    }

    public void setBookingDate(Timestamp bookingDate) {
        this.bookingDate = bookingDate;
    }

    @Basic
    @Column(name = "booking_status", columnDefinition = "TINYINT", length = 1)
    public Integer getBookingStatus() {
        return bookingStatus;
    }

    public void setBookingStatus(Integer bookingStatus) {
        this.bookingStatus = bookingStatus;
    }

    @Basic
    @Column(name = "booking_price")
    public BigDecimal getBookingPrice() {
        return bookingPrice;
    }

    public void setBookingPrice(BigDecimal bookingPrice) {
        this.bookingPrice = bookingPrice;
    }

    @Basic
    @Lob
    @Column(name = "booking_meta")
    public String getBookingMeta() {
        return bookingMeta;
    }

    public void setBookingMeta(String bookingMeta) {
        this.bookingMeta = bookingMeta;
    }

    @Basic
    @Column(name = "booking_tax_rate")
    public BigDecimal getBookingTaxRate() {
        return bookingTaxRate;
    }

    public void setBookingTaxRate(BigDecimal bookingTaxRate) {
        this.bookingTaxRate = bookingTaxRate;
    }

    @Basic
    @Column(name = "booking_taxes")
    public BigDecimal getBookingTaxes() {
        return bookingTaxes;
    }

    public void setBookingTaxes(BigDecimal bookingTaxes) {
        this.bookingTaxes = bookingTaxes;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Bookings that = (Bookings) o;

        if (bookingId != null ? !bookingId.equals(that.bookingId) : that.bookingId != null) return false;
        if (bookingSpaces != null ? !bookingSpaces.equals(that.bookingSpaces) : that.bookingSpaces != null)
            return false;
        if (bookingComment != null ? !bookingComment.equals(that.bookingComment) : that.bookingComment != null)
            return false;
        if (bookingDate != null ? !bookingDate.equals(that.bookingDate) : that.bookingDate != null) return false;
        if (bookingStatus != null ? !bookingStatus.equals(that.bookingStatus) : that.bookingStatus != null)
            return false;
        if (bookingPrice != null ? !bookingPrice.equals(that.bookingPrice) : that.bookingPrice != null) return false;
        if (bookingMeta != null ? !bookingMeta.equals(that.bookingMeta) : that.bookingMeta != null) return false;
        if (bookingTaxRate != null ? !bookingTaxRate.equals(that.bookingTaxRate) : that.bookingTaxRate != null)
            return false;
        if (bookingTaxes != null ? !bookingTaxes.equals(that.bookingTaxes) : that.bookingTaxes != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = bookingId != null ? bookingId.hashCode() : 0;
        result = 31 * result + (bookingSpaces != null ? bookingSpaces.hashCode() : 0);
        result = 31 * result + (bookingComment != null ? bookingComment.hashCode() : 0);
        result = 31 * result + (bookingDate != null ? bookingDate.hashCode() : 0);
        result = 31 * result + (bookingStatus != null ? bookingStatus.hashCode() : 0);
        result = 31 * result + (bookingPrice != null ? bookingPrice.hashCode() : 0);
        result = 31 * result + (bookingMeta != null ? bookingMeta.hashCode() : 0);
        result = 31 * result + (bookingTaxRate != null ? bookingTaxRate.hashCode() : 0);
        result = 31 * result + (bookingTaxes != null ? bookingTaxes.hashCode() : 0);
        return result;
    }
}

Here is the JpaRepository for the Bookings entity:

import com.bigbadcon.dataservices.entity.eventManager.Bookings;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Repository
@Transactional
public interface BookingsRepositoryInterface extends JpaRepository<Bookings, Long> {

    @Query(value = "SELECT b from Bookings b where b.user.id = ?1 AND b.event.eventName not like 'Verify % badge%' " +
            "AND FUNCTION('YEAR', b.event.eventStartDate) = ?2 and b.bookingStatus = 1")
    List<Bookings> findForUser(Long userId, Integer filterYear);

    @Query(value = "SELECT b from Bookings b where b.event.eventId= ?1 and b.bookingSpaces = 1 and b.bookingStatus = 1")
    List<Bookings> findForEvent(Long eventId);

}

And for the TicketsBookings entity:

package com.bigbadcon.dataservices.repository.eventManager.interfaces;

import com.bigbadcon.dataservices.entity.eventManager.TicketsBookings;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Repository
@Transactional
public interface TicketsBookingsRepositoryInterface extends JpaRepository<TicketsBookings, Long> {

    @Query(value = "SELECT tb from TicketsBookings tb where tb.bookingId.event.eventId = ?1 ")
    List<TicketsBookings> findForEvent(Long eventId);
}

And finally, here's the method that's not behaving:

@Service
public class BookingsService {


    @Autowired
    private BookingsRepositoryInterface bookingsDAO;

    @Autowired
    private TicketsRepositoryInterface ticketsDAO;

    @Autowired
    private TicketsBookingsRepositoryInterface ticketsBookingsDAO;

    @Autowired
    private EventsRepositoryInterface eventsDAO;

    @Autowired
    private UsersRepositoryInterface usersDAO;

    @Autowired
    private OptionsRepositoryInterface optionsDAO;

    @Autowired
    private EmailService emailService;

    @Autowired
    private BookingsService bookingsService;

    @Autowired
    private SecurityService securityService;

    @PersistenceContext
    EntityManager em;

    private static final Logger log = LoggerFactory.getLogger(BookingsService.class);

    public HashMap<String, Boolean> stateTable;

    private Integer statusBooked = 1;
    private Integer statusCancelled = 2;
    private Integer statusDeleted = 3;

    /**
     * <p>This method is only to be called by the game registration consumer. It assumes the UI has done some sort
     * of check that the user isn't already booked in the game. The WP UI does this.</p>
     * <p>Admin users can call the addPlayerToGame method instead.</p>
     * @param eventId
     * @param userId
     * @param uuid
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createBooking(Long eventId, Long userId, String uuid) {

        Events event = eventsDAO.findOne(eventId);
        log.info("Event found: " + event.toString());
        Users user = usersDAO.findOne(userId);
        log.info("User found: " + user.toString());

        Timestamp now = new Timestamp(System.currentTimeMillis());

        Tickets ticket = ticketsDAO.findTicketByEventId(event.getEventId());
        if (ticket == null) {
            log.info("Event " + event.getEventId() + " is not open for registration yet.");
            return;
        }
        log.info("Found ticket: " + ticket.toString());

        List<Bookings> bookingsList = bookingsDAO.findForEvent(event.getEventId());
        for (Bookings booking : bookingsList) {
            if (booking.getUser().getId().equals(userId)) {
                log.info("User " + booking.getUser().getDisplayName() + " has already signed up for event id " + event.getEventId());
                return;
            }
        }

        Integer bookingSpaces = 1;
        Integer bookingStatus = 1;
        if (bookingsList.size() >= ticket.getTicketSpaces()) {
            bookingSpaces = 0;
            bookingStatus = 4;
        }

        Bookings booking = new Bookings();
        booking.setEvent(event);
        booking.setUser(user);
        booking.setBookingSpaces(bookingSpaces);
        booking.setBookingDate(now);
        booking.setBookingStatus(bookingStatus);
        booking.setBookingMeta("a:0:{}");
        bookingsDAO.save(booking);

        TicketsBookings ticketBooking = new TicketsBookings();
        ticketBooking.setBookingId(booking);
        ticketBooking.setTicketId(ticket.getTicketId());
        ticketBooking.setTicketBookingSpaces(statusBooked);
        ticketBooking.setTicketBookingPrice(new BigDecimal(0));
        ticketsBookingsDAO.save(ticketBooking);


        //TODO send rejection mail for overbookings
        try {
            if (bookingStatus.equals(1)) {
                emailService.sendEmail(createConfirmedPlayerEmail(user, event));
                emailService.sendEmail(createConfirmedHostEmail(user, event));
                bookingsService.stateTable.put(uuid, new Boolean(true));
            }
            else {
                bookingsService.stateTable.put(uuid, new Boolean(false));
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }

    }

According to what I've read on the subject, methods annotated with @Transactional should commit once the method returns. When I execute the method, it does in fact print to the log that it's saving. However, I have sql debugging turned on, and it does not print any sql statements when it saves. (it does print sql statements on selects.)

The method has no problems doing the selects above, only the saves in the latter part. No exceptions are thrown, but the data doesn't appear in the tables that the entities are using. The only line that gets printed in the log for each save looks like this:

o.s.t.i.TransactionInterceptor           : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]

I'm not sure if this is relevant, but this call is made by the listener class for a jms queue.

My question is, am I missing anything to enable persisting data in this way? From what I've read, @Transactional automatically has readOnly set to true, and my IDE confirms this. Did I miss something important?

  • Just my two cents, MyISAM is not transactional see: https://stackoverflow.com/questions/8036005/myisam-engine-transaction-support ,perhaps you should give a try with InnoDB – garfield Apr 24 '18 at 05:39
  • Might not relate to the issue, but `@Transactional` belongs to the service not repository. – Luay Abdulraheem Apr 24 '18 at 05:55
  • I can't believe I forgot MyISAM isn't transactional. *smacks head* – Jeremy Tidwell Apr 24 '18 at 06:30
  • I altered all the tables to use InnoDB, and removed the @Transactional annotation from all the repository interfaces, and I changed the dialect in the properties file to MySQL5InnoDBDialect. No change in the behavior. – Jeremy Tidwell Apr 24 '18 at 07:24
  • have you confirmed that the method execution does in fact reach the save part? because there are a couple ifs that end up in a return before all those booking and ticket saves – Zeromus Apr 24 '18 at 07:51
  • Yes, I've run it through the debugger to be sure. It gets there, that's how I got the log output for the save statements. – Jeremy Tidwell Apr 24 '18 at 08:12
  • I checked the MariaDB logs, there are no queries sent to the DB when the app calls .save() . I'm not sure how to proceed here. – Jeremy Tidwell Apr 25 '18 at 06:39

1 Answers1

0

For anyone reading this in the future, the root cause of the issue was that the service method call was coming from a message queue receiver class. I'm not sure why. When I moved the call directly to the web service instead of the queue receiver, the insert happened with no issues.