HHH-5578 Add support for SpecJ's proprietary syntax

Initial patch by Stale
Uses a specific property to activate
Fix a couple of test issues (The test made change on an unmanaged object wo reattaching it)
Fix some code style issues
Add TODOs for a proper solution
Use the correct PropertyData
  Populate the metamodel with the PropertyData
  corresponding to the @ToOne instead of reusing the
  PropertyData from the @Id property

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@20766 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Emmanuel Bernard 2010-10-04 09:44:12 +00:00
parent e1cea7d8e1
commit 3684576124
8 changed files with 796 additions and 4 deletions

View File

@ -1412,6 +1412,33 @@ public final class AnnotationBinder {
final XAnnotatedElement element = propertyAnnotatedElement.getProperty();
if ( element.isAnnotationPresent( Id.class ) || element.isAnnotationPresent( EmbeddedId.class ) ) {
annElts.add( 0, propertyAnnotatedElement );
/**
* The property must be put in hibernate.properties as it's a system wide property. Fixable?
* TODO support true/false/default on the property instead of present / not present
* TODO is @Column mandatory?
* TODO add method support
* TODO avoid custId hardcoded
*/
if ( System.getProperty( "hibernate.enable_specj_proprietary_syntax" ) != null ) {
if ( element.isAnnotationPresent( Id.class ) && element.isAnnotationPresent( Column.class ) ) {
String columnName = element.getAnnotation( Column.class ).name();
for ( XProperty prop : declaringClass.getDeclaredProperties( AccessType.FIELD.getType() ) ) {
if ( prop.isAnnotationPresent( JoinColumn.class )
&& prop.getAnnotation( JoinColumn.class ).name().equals( columnName )
&& !prop.isAnnotationPresent( MapsId.class ) ) {
//create a PropertyData fpr the specJ property holding the mapping
PropertyData specJPropertyData = new PropertyInferredData(
declaringClass, //same dec
prop, // the actual @XToOne property
propertyAccessor, //TODO we should get the right accessor but the same as id would do
mappings.getReflectionManager()
);
mappings.addPropertyAnnotatedWithMapsIdSpecj( entity, specJPropertyData, "custId" );
}
}
}
}
if ( element.isAnnotationPresent( ManyToOne.class ) || element.isAnnotationPresent( OneToOne.class ) ) {
mappings.addToOneAndIdProperty( entity, propertyAnnotatedElement );
}
@ -2622,6 +2649,31 @@ public final class AnnotationBinder {
column.setUpdatable( false );
}
}
//Make sure that JPA1 key-many-to-one columns are read only too
if ( System.getProperty( "hibernate.enable_specj_proprietary_syntax" ) != null ) {
String columnName = "";
for ( XProperty prop : inferredData.getDeclaringClass()
.getDeclaredProperties( AccessType.FIELD.getType() ) ) {
if ( prop.isAnnotationPresent( Id.class ) && prop.isAnnotationPresent( Column.class ) ) {
columnName = prop.getAnnotation( Column.class ).name();
}
final JoinColumn joinColumn = prop.getAnnotation( JoinColumn.class );
if ( prop.isAnnotationPresent( ManyToOne.class ) && joinColumn != null
&& !joinColumn.name().isEmpty()
&& joinColumn.name().equals( columnName )
&& !prop.isAnnotationPresent( MapsId.class ) )
{
for ( Ejb3JoinColumn column : columns ) {
column.setInsertable( false );
column.setUpdatable( false );
}
}
}
}
value.setTypeName( inferredData.getClassOrElementName() );
final String propertyName = inferredData.getPropertyName();
value.setTypeUsingReflection( propertyHolder.getClassName(), propertyName );

View File

@ -3595,6 +3595,15 @@ public class Configuration implements Serializable {
map.put( property.getProperty().getAnnotation( MapsId.class ).value(), property );
}
public void addPropertyAnnotatedWithMapsIdSpecj(XClass entityType, PropertyData property, String mapsIdValue) {
Map<String, PropertyData> map = propertiesAnnotatedWithMapsId.get( entityType );
if ( map == null ) {
map = new HashMap<String, PropertyData>();
propertiesAnnotatedWithMapsId.put( entityType, map );
}
map.put( mapsIdValue, property );
}
public PropertyData getPropertyAnnotatedWithIdAndToOne(XClass entityType, String propertyName) {
final Map<String, PropertyData> map = propertiesAnnotatedWithIdAndToOne.get( entityType );
return map == null ? null : map.get( propertyName );

View File

@ -733,6 +733,8 @@ public interface Mappings {
public void addPropertyAnnotatedWithMapsId(XClass entityType, PropertyData property);
public void addPropertyAnnotatedWithMapsIdSpecj(XClass entityType, PropertyData property, String mapsIdValue);
/**
* Should we use the new generator strategy mappings. This is controlled by the
* {@link Configuration#USE_NEW_ID_GENERATOR_MAPPINGS} setting.
@ -748,7 +750,4 @@ public interface Mappings {
public PropertyData getPropertyAnnotatedWithIdAndToOne(XClass entityType, String propertyName);
void addToOneAndIdProperty(XClass entity, PropertyData property);
}

View File

@ -0,0 +1,243 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, Red Hat, Inc. and/or its affiliates or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat, Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.annotations.derivedidentities.e1.b.specjmapid;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;
@SuppressWarnings("serial")
@NamedQueries({
@NamedQuery(name = Customer.QUERY_ALL,
query = "select a from Customer a"),
@NamedQuery(name = Customer.QUERY_COUNT,
query = "select COUNT(a) from Customer a"),
@NamedQuery(name = Customer.QUERY_BY_CREDIT,
query = "SELECT c.id FROM Customer c WHERE c.creditLimit > :limit")
})
@Entity
@Table(name = "O_CUSTOMER")
public class Customer implements Serializable {
public static final String QUERY_ALL = "Customer.selectAll";
public static final String QUERY_COUNT = "Customer.count";
public static final String QUERY_BY_CREDIT = "Customer.selectByCreditLimit";
public static final String BAD_CREDIT = "BC";
@Id
@Column(name = "C_ID")
private int id;
@Column(name = "C_FIRST")
private String firstName;
@Column(name = "C_LAST")
private String lastName;
@Column(name = "C_CONTACT")
private String contact;
@Column(name = "C_CREDIT")
private String credit;
@Column(name = "C_CREDIT_LIMIT")
private BigDecimal creditLimit;
@Column(name = "C_SINCE")
@Temporal(TemporalType.DATE)
private Calendar since;
@Column(name = "C_BALANCE")
private BigDecimal balance;
@Column(name = "C_YTD_PAYMENT")
private BigDecimal ytdPayment;
@OneToMany(targetEntity = CustomerInventory.class,
mappedBy = "customer",
cascade = CascadeType.ALL,
fetch = FetchType.EAGER)
private List<CustomerInventory> customerInventories;
@Version
@Column(name = "C_VERSION")
private int version;
protected Customer() {
}
public Customer(String first, String last,
String contact, String credit, BigDecimal creditLimit,
BigDecimal balance, BigDecimal YtdPayment) {
this.firstName = first;
this.lastName = last;
this.contact = contact;
this.since = Calendar.getInstance();
this.credit = credit;
this.creditLimit = creditLimit;
this.balance = balance;
this.ytdPayment = YtdPayment;
}
public Integer getId() {
return id;
}
public void setId(Integer customerId) {
this.id = customerId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getContact() {
return contact;
}
public void setContact(String contact) {
this.contact = contact;
}
public String getCredit() {
return credit;
}
public void setCredit(String credit) {
this.credit = credit;
}
public BigDecimal getCreditLimit() {
return creditLimit;
}
public void setCreditLimit(BigDecimal creditLimit) {
this.creditLimit = creditLimit;
}
public Calendar getSince() {
return since;
}
public void setSince(Calendar since) {
this.since = since;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
public void changeBalance(BigDecimal change) {
setBalance( balance.add( change ).setScale( 2, BigDecimal.ROUND_DOWN ) );
}
public BigDecimal getYtdPayment() {
return ytdPayment;
}
public void setYtdPayment(BigDecimal ytdPayment) {
this.ytdPayment = ytdPayment;
}
public List<CustomerInventory> getInventories() {
if ( customerInventories == null ) {
customerInventories = new ArrayList<CustomerInventory>();
}
return customerInventories;
}
public CustomerInventory addInventory(Item item, int quantity,
BigDecimal totalValue) {
CustomerInventory inventory = new CustomerInventory(
this, item,
quantity, totalValue
);
getInventories().add( inventory );
return inventory;
}
public int getVersion() {
return version;
}
public boolean hasSufficientCredit(BigDecimal amount) {
return !BAD_CREDIT.equals( getCredit() )
&& creditLimit != null
&& creditLimit.compareTo( amount ) >= 0;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
return id == ( ( Customer ) o ).id;
}
@Override
public int hashCode() {
return new Integer( id ).hashCode();
}
@Override
public String toString() {
return this.getFirstName() + " " + this.getLastName();
}
}

View File

@ -0,0 +1,150 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, Red Hat, Inc. and/or its affiliates or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat, Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.annotations.derivedidentities.e1.b.specjmapid;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Comparator;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import javax.persistence.Version;
@NamedQueries({
@NamedQuery(name = "CustomerInventory.selectAll",
query = "select a from CustomerInventory a")
})
@SuppressWarnings("serial")
@Entity
@Table(name = "O_CUSTINVENTORY")
@IdClass(CustomerInventoryPK.class)
public class CustomerInventory implements Serializable, Comparator<CustomerInventory> {
@Id
@TableGenerator(name = "inventory",
table = "U_SEQUENCES",
pkColumnName = "S_ID",
valueColumnName = "S_NEXTNUM",
pkColumnValue = "inventory",
allocationSize = 1000)
@GeneratedValue(strategy = GenerationType.TABLE, generator = "inventory")
@Column(name = "CI_ID")
private Integer id;
@Id
@Column(name = "CI_CUSTOMERID", insertable = false, updatable = false)
private int custId;
@ManyToOne(cascade = CascadeType.MERGE)
@JoinColumn(name = "CI_CUSTOMERID")
private Customer customer;
@ManyToOne(cascade = CascadeType.MERGE)
@JoinColumn(name = "CI_ITEMID")
private Item vehicle;
@Column(name = "CI_VALUE")
private BigDecimal totalCost;
@Column(name = "CI_QUANTITY")
private int quantity;
@Version
@Column(name = "CI_VERSION")
private int version;
protected CustomerInventory() {
}
CustomerInventory(Customer customer, Item vehicle, int quantity, BigDecimal totalValue) {
this.customer = customer;
this.vehicle = vehicle;
this.quantity = quantity;
this.totalCost = totalValue;
}
public Item getVehicle() {
return vehicle;
}
public BigDecimal getTotalCost() {
return totalCost;
}
public int getQuantity() {
return quantity;
}
public Integer getId() {
return id;
}
public Customer getCustomer() {
return customer;
}
public int getCustId() {
return custId;
}
public int getVersion() {
return version;
}
public int compare(CustomerInventory cdb1, CustomerInventory cdb2) {
return cdb1.id.compareTo( cdb2.id );
}
@Override
public boolean equals(Object obj) {
if ( obj == this ) {
return true;
}
if ( obj == null || !( obj instanceof CustomerInventory ) ) {
return false;
}
if ( this.id == ( ( CustomerInventory ) obj ).id ) {
return true;
}
if ( this.id != null && ( ( CustomerInventory ) obj ).id == null ) {
return false;
}
if ( this.id == null && ( ( CustomerInventory ) obj ).id != null ) {
return false;
}
return this.id.equals( ( ( CustomerInventory ) obj ).id );
}
}

View File

@ -0,0 +1,67 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, Red Hat, Inc. and/or its affiliates or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat, Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.annotations.derivedidentities.e1.b.specjmapid;
import java.io.Serializable;
public class CustomerInventoryPK implements Serializable {
private Integer id;
private int custId;
public CustomerInventoryPK() {
}
public CustomerInventoryPK(Integer id, int custId) {
this.id = id;
this.custId = custId;
}
public boolean equals(Object other) {
if ( other == this ) {
return true;
}
if ( other == null || getClass() != other.getClass() ) {
return false;
}
CustomerInventoryPK cip = ( CustomerInventoryPK ) other;
return ( custId == cip.custId && ( id == cip.id ||
( id != null && id.equals( cip.id ) ) ) );
}
public int hashCode() {
return ( id == null ? 0 : id.hashCode() ) ^ custId;
}
public Integer getId() {
return id;
}
public int getCustId() {
return custId;
}
}

View File

@ -0,0 +1,128 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, Red Hat, Inc. and/or its affiliates or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat, Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.annotations.derivedidentities.e1.b.specjmapid;
import java.math.BigDecimal;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.test.annotations.TestCase;
/**
* A test.
*
* @author <a href="mailto:stale.pedersen@jboss.org">Stale W. Pedersen</a>
*/
public class IdMapManyToOneSpecjTest extends TestCase {
public IdMapManyToOneSpecjTest() {
System.setProperty( "hibernate.enable_specj_proprietary_syntax", "true" );
}
public void testComplexIdClass() {
Session s = openSession();
Transaction tx = s.beginTransaction();
Customer c1 = new Customer(
"foo", "bar", "contact1", "100", new BigDecimal( 1000 ), new BigDecimal( 1000 ), new BigDecimal( 1000 )
);
s.persist( c1 );
s.flush();
s.clear();
Item boat = new Item();
boat.setId( "1" );
boat.setName( "cruiser" );
boat.setPrice( new BigDecimal( 500 ) );
boat.setDescription( "a boat" );
boat.setCategory( 42 );
s.persist( boat );
Item house = new Item();
house.setId( "2" );
house.setName( "blada" );
house.setPrice( new BigDecimal( 5000 ) );
house.setDescription( "a house" );
house.setCategory( 74 );
s.persist( house );
s.flush();
s.clear();
c1.addInventory( boat, 10, new BigDecimal( 5000 ) );
c1.addInventory( house, 100, new BigDecimal( 50000 ) );
s.merge( c1 );
s.flush();
s.clear();
Customer c12 = ( Customer ) s.createQuery( "select c from Customer c" ).uniqueResult();
// c12.getBalance();
List<CustomerInventory> inventory = c12.getInventories();
assertEquals( 2, inventory.size() );
assertEquals( 10, inventory.get( 0 ).getQuantity() );
Item house2 = new Item();
house2.setId( "3" );
house2.setName( "blada" );
house2.setPrice( new BigDecimal( 5000 ) );
house2.setDescription( "a house" );
house2.setCategory( 74 );
s.persist( house2 );
s.flush();
s.clear();
c12.addInventory( house2, 200, new BigDecimal( 500000 ) );
s.merge( c12 );
s.flush();
s.clear();
Customer c13 = ( Customer ) s.createQuery( "select c from Customer c where c.id = " + c12.getId() )
.uniqueResult();
assertEquals( 3, c13.getInventories().size() );
tx.rollback();
s.close();
}
protected Class[] getAnnotatedClasses() {
return new Class[] {
Customer.class,
CustomerInventory.class,
CustomerInventoryPK.class,
Item.class
};
}
}

View File

@ -0,0 +1,144 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, Red Hat, Inc. and/or its affiliates or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat, Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.annotations.derivedidentities.e1.b.specjmapid;
import java.io.Serializable;
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Version;
@NamedQueries({
@NamedQuery(name = "Item.findByCategory",
query = "SELECT i FROM Item i WHERE i.category=:category ORDER BY i.id")
})
@SuppressWarnings("serial")
@Entity
@Table(name = "O_ITEM")
public class Item implements Serializable {
public static final String QUERY_BY_CATEGORY = "Item.findByCategory";
@Id
@Column(name = "I_ID")
private String id;
@Column(name = "I_NAME")
private String name;
@Column(name = "I_PRICE")
private BigDecimal price;
@Column(name = "I_DESC")
private String description;
@Column(name = "I_DISCOUNT")
private BigDecimal discount;
@Column(name = "I_CATEGORY")
private int category;
@Version
@Column(name = "I_VERSION")
int version;
public String getId() {
return id;
}
public void setId(String i) {
id = i;
}
public int getCategory() {
return category;
}
public void setCategory(int category) {
this.category = category;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public BigDecimal getDiscount() {
return discount;
}
public void setDiscount(BigDecimal discount) {
if ( discount.doubleValue() < 0 || discount.doubleValue() > 100.0 ) {
throw new IllegalArgumentException(
this + " discount " + discount
+ " is invalid. Must be between 0.0 and 100.0"
);
}
this.discount = discount;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public int getVersion() {
return version;
}
@Override
public boolean equals(Object other) {
if ( other == null || other.getClass() != this.getClass() ) {
return false;
}
if ( other == this ) {
return true;
}
return id.equals( ( ( Item ) other ).id );
}
@Override
public int hashCode() {
return id.hashCode();
}
}