Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add very first money types handling #455

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions hibernate-types-4/pom.xml
Expand Up @@ -47,6 +47,14 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>${moneta.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
Expand All @@ -70,6 +78,7 @@

<jackson.version>2.7.9.6</jackson.version>
<guava.version>12.0</guava.version>
<moneta.version>1.4.2</moneta.version>
</properties>

<build>
Expand Down
@@ -0,0 +1,126 @@
package com.vladmihalcea.hibernate.type.money;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.BigDecimalType;
import org.hibernate.type.StringType;
import org.hibernate.type.Type;
import org.hibernate.usertype.CompositeUserType;
import org.javamoney.moneta.Money;

import javax.money.MonetaryAmount;
import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import static java.sql.Types.DECIMAL;
import static java.sql.Types.VARCHAR;

/**
* Maps a {@link MonetaryAmount} object type to composite columns (with amount and with currency).
*
* @author Piotr Olaszewski
*/
public class MoneyAmountAndCurrencyType implements CompositeUserType {
@Override
public String[] getPropertyNames() {
return new String[]{"amount", "currency"};
}

@Override
public Type[] getPropertyTypes() {
return new Type[]{BigDecimalType.INSTANCE, StringType.INSTANCE};
}

@Override
public Object getPropertyValue(Object component, int property) throws HibernateException {
MonetaryAmount monetaryAmount = (MonetaryAmount) component;
if (property == 0) {
return monetaryAmount.getNumber().numberValue(BigDecimal.class);
} else if (property == 1) {
return monetaryAmount.getCurrency();
}

throw new IllegalArgumentException("Invalid property index " + property + " for class " + component.getClass());
}

@Override
public void setPropertyValue(Object component, int property, Object value) throws HibernateException {
throw new HibernateException("Call setPropertyValue on immutable type " + component.getClass());
}

@Override
public Class returnedClass() {
return MonetaryAmount.class;
}

@Override
public boolean equals(Object x, Object y) throws HibernateException {
return (x == y) || (x != null && x.equals(y));
}

@Override
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}

@Override
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
if (rs.wasNull()) {
return null;
}

String amountColumnName = names[0];
String currencyColumnName = names[1];

BigDecimal amount = rs.getBigDecimal(amountColumnName);
String currency = rs.getString(currencyColumnName);

return Money.of(amount, currency);
}

@Override
public void nullSafeSet(PreparedStatement st, Object value, int amountColumnIndex, SessionImplementor session) throws HibernateException, SQLException {
int currencyColumnIndex = amountColumnIndex + 1;

if (value == null) {
st.setNull(amountColumnIndex, DECIMAL);
st.setNull(currencyColumnIndex, VARCHAR);
} else {
MonetaryAmount monetaryAmount = (MonetaryAmount) value;

BigDecimal amount = monetaryAmount.getNumber().numberValue(BigDecimal.class);
String currency = monetaryAmount.getCurrency().getCurrencyCode();

st.setBigDecimal(amountColumnIndex, amount);
st.setString(currencyColumnIndex, currency);
}
}

@Override
public Object deepCopy(Object value) throws HibernateException {
return value;
}

@Override
public boolean isMutable() {
return false;
}

@Override
public Serializable disassemble(Object value, SessionImplementor session) throws HibernateException {
return (Serializable) value;
}

@Override
public Object assemble(Serializable cached, SessionImplementor session, Object owner) throws HibernateException {
return cached;
}

@Override
public Object replace(Object original, Object target, SessionImplementor session, Object owner) throws HibernateException {
return original;
}
}
@@ -0,0 +1,117 @@
package com.vladmihalcea.hibernate.type.money;

import com.vladmihalcea.hibernate.util.AbstractMySQLIntegrationTest;
import com.vladmihalcea.hibernate.util.transaction.JPATransactionFunction;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.javamoney.moneta.Money;
import org.junit.Test;

import javax.money.MonetaryAmount;
import javax.persistence.*;
import java.math.BigDecimal;

import static org.junit.Assert.assertEquals;

/**
* @author Piotr Olaszewski
*/
public class MySQLMoneyAmountAndCurrencyTypeTest extends AbstractMySQLIntegrationTest {
@Override
protected Class<?>[] entities() {
return new Class[]{
Salary.class
};
}

@Test
public void testPersistAndReadMoney() {
final Salary _salary = doInJPA(new JPATransactionFunction<Salary>() {
@Override
public Salary apply(EntityManager entityManager) {
Salary salary = new Salary();
salary.setSalary(Money.of(new BigDecimal("10.23"), "USD"));

entityManager.persist(salary);

return salary;
}
});

doInJPA(new JPATransactionFunction<Void>() {
@Override
public Void apply(EntityManager entityManager) {
Salary salary = entityManager.find(Salary.class, _salary.getId());

assertEquals(salary.getSalary(), Money.of(new BigDecimal("10.23"), "USD"));

return null;
}
});
}

@Test
public void testSearchByMoney() {
doInJPA(new JPATransactionFunction<Void>() {
@Override
public Void apply(EntityManager entityManager) {
Salary salary1 = new Salary();
salary1.setSalary(Money.of(new BigDecimal("10.23"), "USD"));
entityManager.persist(salary1);

Salary salary2 = new Salary();
salary2.setSalary(Money.of(new BigDecimal("20.23"), "EUR"));
entityManager.persist(salary2);

return null;
}
});

doInJPA(new JPATransactionFunction<Void>() {
@Override
public Void apply(EntityManager entityManager) {
Money money = Money.of(new BigDecimal("10.23"), "USD");
Salary salary = entityManager.createQuery("select s from Salary s where salary = :salary", Salary.class)
.setParameter("salary", money)
.getSingleResult();

assertEquals(Long.valueOf(1), salary.getId());

return null;
}
});
}

@Entity(name = "Salary")
@Table(name = "salary")
@TypeDef(name = "monetary-amount-currency", typeClass = MoneyAmountAndCurrencyType.class, defaultForType = MonetaryAmount.class)
public static class Salary {
@Id
@GeneratedValue
private Long id;

@Columns(columns = {
@Column(name = "salary_amount"),
@Column(name = "salary_currency")
})
@Type(type = "monetary-amount-currency")
private MonetaryAmount salary;

public Long getId() {
return id;
}

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

public MonetaryAmount getSalary() {
return salary;
}

public void setSalary(MonetaryAmount salary) {
this.salary = salary;
}
}
}
@@ -0,0 +1,117 @@
package com.vladmihalcea.hibernate.type.money;

import com.vladmihalcea.hibernate.util.AbstractPostgreSQLIntegrationTest;
import com.vladmihalcea.hibernate.util.transaction.JPATransactionFunction;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.javamoney.moneta.Money;
import org.junit.Test;

import javax.money.MonetaryAmount;
import javax.persistence.*;
import java.math.BigDecimal;

import static org.junit.Assert.assertEquals;

/**
* @author Piotr Olaszewski
*/
public class PostgreSQLMoneyAmountAndCurrencyTypeTest extends AbstractPostgreSQLIntegrationTest {
@Override
protected Class<?>[] entities() {
return new Class[]{
Salary.class
};
}

@Test
public void testPersistAndReadMoney() {
final Salary _salary = doInJPA(new JPATransactionFunction<Salary>() {
@Override
public Salary apply(EntityManager entityManager) {
Salary salary = new Salary();
salary.setSalary(Money.of(new BigDecimal("10.23"), "USD"));

entityManager.persist(salary);

return salary;
}
});

doInJPA(new JPATransactionFunction<Void>() {
@Override
public Void apply(EntityManager entityManager) {
Salary salary = entityManager.find(Salary.class, _salary.getId());

assertEquals(salary.getSalary(), Money.of(new BigDecimal("10.23"), "USD"));

return null;
}
});
}

@Test
public void testSearchByMoney() {
doInJPA(new JPATransactionFunction<Void>() {
@Override
public Void apply(EntityManager entityManager) {
Salary salary1 = new Salary();
salary1.setSalary(Money.of(new BigDecimal("10.23"), "USD"));
entityManager.persist(salary1);

Salary salary2 = new Salary();
salary2.setSalary(Money.of(new BigDecimal("20.23"), "EUR"));
entityManager.persist(salary2);

return null;
}
});

doInJPA(new JPATransactionFunction<Void>() {
@Override
public Void apply(EntityManager entityManager) {
Money money = Money.of(new BigDecimal("10.23"), "USD");
Salary salary = entityManager.createQuery("select s from Salary s where salary = :salary", Salary.class)
.setParameter("salary", money)
.getSingleResult();

assertEquals(Long.valueOf(1), salary.getId());

return null;
}
});
}

@Entity(name = "Salary")
@Table(name = "salary")
@TypeDef(name = "monetary-amount-currency", typeClass = MoneyAmountAndCurrencyType.class, defaultForType = MonetaryAmount.class)
public static class Salary {
@Id
@GeneratedValue
private Long id;

@Columns(columns = {
@Column(name = "salary_amount"),
@Column(name = "salary_currency")
})
@Type(type = "monetary-amount-currency")
private MonetaryAmount salary;

public Long getId() {
return id;
}

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

public MonetaryAmount getSalary() {
return salary;
}

public void setSalary(MonetaryAmount salary) {
this.salary = salary;
}
}
}