I'm new to Hibernate. I'm using Hibernate 4.0 and specifically JPA annotations.
I have an Order class. An order can contain multiple Skus ('sku' is a stock keeping unit - see Wikipedia) and one or more of each sku. The Order and Sku classes are below. I'd like to persist the order together with its associated skus in the same table but am open to using two tables if needed. I can't work out how to persist the HashMap - the runtime exception thrown states javax.persistence.PersistenceException: org.hibernate.exception.DataException: Data truncation: Data too long for column 'skusInOrder' at row 1
.
I have read [this][2] Stackoverflow question: - does the advice to use the @Lob annotation make sense in my scenario? Do I need to use @Embedded or @Embeddable and/or @ElementCollection?
package com.newco.drinks.data;
import java.io.Serializable;
import java.math.RoundingMode;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.MapKey;
import javax.persistence.OneToMany;
import javax.persistence.SecondaryTable;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
@Entity
@Table(name="Orders")
public class Order implements Serializable {
private static final long serialVersionUID = 5915005362337363656L;
private List<Sku> availableSkus;
private String barId;
private java.util.Date collectionNotificationTime;
private CurrencyUnit currencyUnit;
private String customerId;
private String orderId;
private Money orderTotal;
private String paymentProcessorRef;
private Boolean paymentSuccessful;
private HashMap<Sku, Integer> skusInOrder = new HashMap<Sku, Integer>(); // skuId and quantity thereof
private java.util.Date timeReceived;
/**
*
* @param sku
* @param quantity
* @throws IllegalArgumentException
* if quantity is zero or negative, or if the Order does not
* contain the Sku
* @throws ConcurrentModificationException
*/
void add(Sku sku, int quantity) throws IllegalArgumentException, ConcurrentModificationException {
if (quantity <= 0) {
throw new IllegalArgumentException("Quantity must be greater than zero, but was " + quantity);
}
if (skusInOrder.isEmpty() || !skusInOrder.containsKey(sku)) {
skusInOrder.put(sku, quantity);
} else {
if (skusInOrder.containsKey(sku)) {
int i = skusInOrder.get(sku);
skusInOrder.put(sku, i + quantity);
} else {
throw new IllegalArgumentException("Order " + getOrderId() + " does not contain SKU " + sku.getSkuId());
}
}
}
private Money calculateOrderTotal() {
int decimalPlaces = currencyUnit.getDecimalPlaces();
String zeroString = null;
switch (decimalPlaces) {
case 1:
zeroString = "0.0";
break;
case 2:
zeroString = "0.00";
break;
case 3:
zeroString = "0.000";
break;
case 4:
zeroString = "0.0000";
break;
}
Money total = Money.of(currencyUnit, new Double(zeroString));
for (Sku sku : skusInOrder.keySet()) {
int quantity = skusInOrder.get(sku);
Money totalExTax = sku.getPrice().multipliedBy(quantity, RoundingMode.HALF_UP);
double taxRate = sku.getTaxRate();
if (taxRate > 0) {
Money tax = totalExTax.multipliedBy(taxRate / 100, RoundingMode.HALF_UP);
total = total.plus(totalExTax.plus(tax));
} else {
total = total.plus(totalExTax);
}
}
return total;
}
/**
*
* @return a List of Sku objects available to be added to this order.
* Different bars will have different Skus available.
*/
@Transient
@OneToMany
public List<Sku> getAvailableSkus() {
return availableSkus;
}
@Column(name="barid", nullable=false)
public String getBarId() {
return barId;
}
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "collectionnotificationtime")
public java.util.Date getCollectionNotificationTime() {
return collectionNotificationTime;
}
@Column(name="currencyunit", nullable=false)
public CurrencyUnit getCurrencyUnit() {
return currencyUnit;
}
@Column(name="customerid", nullable=false)
public String getCustomerId() {
return customerId;
}
@Id
@Column(name="orderid", nullable=false)
public String getOrderId() {
return orderId;
}
//@Column(name="ordertotal", nullable=false)
@Transient
public Money getOrderTotal() {
return calculateOrderTotal();
}
@Column(name="paymentprocessorref", nullable=true)
public String getPaymentProcessorRef() {
return paymentProcessorRef;
}
@Column(name="paymentsuccess", nullable=true)
public Boolean getPaymentSuccessful() {
return paymentSuccessful;
}
@Column(name="skusinorder", nullable=false)
public HashMap<Sku, Integer> getSkusInOrder() {
return skusInOrder;
}
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "timereceived")
public java.util.Date getTimeReceived() {
return timeReceived;
}
/**
*
* @param sku
* @param quantity
* @throws IllegalArgumentException
* if quantity is zero or negative, or if the Sku does not form
* part of the Order
* @throws ConcurrentModificationException
*/
void remove(Sku sku, int quantity) throws IllegalArgumentException, ConcurrentModificationException {
if (quantity <= 0) {
throw new IllegalArgumentException("Quantity to remove must be greater than zero for order " + getOrderId() + ", but was " + quantity);
}
if (skusInOrder.isEmpty() || !skusInOrder.containsKey(sku)) {
throw new IllegalArgumentException("Cannot remove sku " + sku.getSkuId() + " which doesn't form part of order " + getOrderId());
} else {
int i = skusInOrder.get(sku);
if (quantity <= i) { // fine, this is expected
skusInOrder.put(sku, i - quantity);
} else if (quantity == i) { //okay, remove that sku altogether as its quantity is now zero
skusInOrder.remove(sku);
}
}
}
void setAvailableSkus(List<Sku> availableSkus) {
this.availableSkus = availableSkus;
}
void setBarId(String barId) {
this.barId = barId;
}
void setCollectionNotificationTime(
java.util.Date collectionNotificationTime) {
this.collectionNotificationTime = collectionNotificationTime;
}
void setCollectionReadySent(java.util.Date collectionReadySent) {
this.collectionNotificationTime = collectionReadySent;
}
void setCurrencyUnit(CurrencyUnit currencyUnit) {
this.currencyUnit = currencyUnit;
}
void setCustomerId(String customerId) {
this.customerId = customerId;
}
void setOrderId(String orderId) {
this.orderId = orderId;
}
void setOrderTotal(){
orderTotal = calculateOrderTotal();
}
void setPaymentProcessorRef(String paymentProcessorRef) {
this.paymentProcessorRef = paymentProcessorRef;
}
void setPaymentSuccessful(Boolean paymentSuccessful) {
this.paymentSuccessful = paymentSuccessful;
}
}
package com.newco.drinks.data;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
@Entity
@Table( name = "Skus" )
public class Sku implements Serializable{
private static final long serialVersionUID = 8375466982619713795L;
private String skuId;
private String barId;
private CurrencyUnit currencyUnit;
private double price;
private String description;
private String brand;
private String skuType;
private double taxRate;
protected Sku(){
// no args constructor for Hibernate use
}
public Sku(String skuId, String barId, CurrencyUnit currencyUnit, double price, String description, String brand, String skuType, double tax) {
setSkuId(skuId);
setBarId(barId);
setCurrencyUnit(currencyUnit);
setPrice(price);
setDescription(description);
setBrand(brand);
setSkuType(skuType);
setTaxRate(tax);
}
@Column(name="barid", nullable=false)
public String getBarId() {
return barId;
}
@Column(name="brand", nullable=true)
public String getBrand() {
return brand;
}
@Column(name="currencyunit", nullable=false)
public CurrencyUnit getCurrencyUnit() {
return currencyUnit;
}
@Column(name="description", nullable=false)
public String getDescription() {
return description;
}
@Column(name="price", nullable=false)
public Money getPrice() {
return Money.of(currencyUnit, price);
}
@Id
@Column(name="skuid", nullable=false)
public String getSkuId() {
return skuId;
}
@Column(name="skutype", nullable=false)
public String getSkuType() {
return skuType;
}
@Column(name="taxrate", nullable=false)
public double getTaxRate() {
return taxRate;
}
void setBarId(String barId) {
this.barId = barId;
}
void setBrand(String brand) {
this.brand = brand;
}
void setCurrencyUnit(CurrencyUnit currencyUnit) {
this.currencyUnit = currencyUnit;
}
void setDescription(String description) {
this.description = description;
}
void setPrice(double price) {
this.price = price;
}
void setSkuId(String skuId) {
this.skuId = skuId;
}
void setSkuType(String skuType) {
this.skuType = skuType;
}
void setTaxRate(double tax) {
this.taxRate = tax;
}
}
[2]: How to persist a HashMap with hibernate, Integer