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?