Friday, February 17, 2012

Hibernate in Spring 3

Hibernate is something that I've done a couple of tutorials on in the past. Basically, to me it's been something that lets you map in XML what fields in the object correspond to what fields in the db, so you don't have to code JDBC statements.

But, I ran the hibernate wizard on spring 3 templates, and it actually seems as if it's close to the Groovy version, which means there aren't any mappings to be done at all. Let's find out why.

Checking app-context.xml, we find the following 3 statements

<jdbc:embedded-database id="dataSource" type="H2"/>

<tx:annotation-driven transaction-manager="transactionManager" />

<context:component-scan base-package="xyz.sample.barehibernate" />

So, it looks like it's using an embedded HQL data source, supports transactions, and scans the package for components.

Next, let's look at the source code the template wizard create. First, there's a class called "Hibernate Configuration" which ought to be interesting:

I'll add comments to show my guesses as to what the code means:



package xyz.sample.barehibernate;

import java.util.Properties;

import javax.sql.DataSource;

import org.hibernate.dialect.H2Dialect;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.hibernate3.HibernateTransactionManager;
import org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean;

@Configuration // this is used for configuration - not sure what that exactly means, but it's a java configuration vs. xml
// configuration
public class HibernateConfiguration {

@Value("#{dataSource}") // clearly pulling in the data source from app-context.xml.
private DataSource dataSource;

@Bean // A bean which will be scanned by the container
public AnnotationSessionFactoryBean sessionFactoryBean() { // method to generate an annotation-based Hibernate
// session, used by the container
Properties props = new Properties();
props.put("hibernate.dialect", H2Dialect.class.getName()); // set up the HSQL dialect to be used
props.put("hibernate.format_sql", "true"); // we like SQL

AnnotationSessionFactoryBean bean = new AnnotationSessionFactoryBean(); // private instantiation
bean.setAnnotatedClasses(new Class[]{Item.class, Order.class}); // give it the classes to be "hibernated"
// this is the "magic" that saves mapping
bean.setHibernateProperties(props); // set the props
bean.setDataSource(this.dataSource); // set the data source
bean.setSchemaUpdate(true); // allow updates/
return bean;
}


// Give whoever needs the transaction manager the session factory bean decorated with the
// Hibernate transaction manager

@Bean
public HibernateTransactionManager transactionManager() {
return new HibernateTransactionManager( sessionFactoryBean().getObject() );
}

}




Now the domain classes, again with comments:

package xyz.sample.barehibernate;


import java.util.Collection;
import java.util.LinkedHashSet;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;


/**
* An order.
*/
@Entity // It's a domain object
@Table(name="T_ORDER") // here's the table name
public class Order {

@Id // this is the id
@GeneratedValue(strategy=GenerationType.AUTO) // and it should be auto-generated
private Long id; // make it a long

private String customer;

@OneToMany(cascade=CascadeType.ALL) // it can have many items
@JoinColumn(name="ORDER_ID") // here's the column name to join it on
private Collection items = new LinkedHashSet(); // and the list of items

// getters and setters

/**
* @return the customer
*/
public String getCustomer() {
return customer;
}

/**
* @param customer the customer to set
*/
public void setCustomer(String customer) {
this.customer = customer;
}

/**
* @return the items
*/
public Collection getItems() {
return items;
}

/**
* @param items the items to set
*/
public void setItems(Collection items) {
this.items = items;
}

/**
* @return the id
*/
public Long getId() {
return id;
}

}

// this is the order header

package xyz.sample.barehibernate;


import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

/**
* An item in an order
*/
@Entity // It's a domain object (persistable)
public class Item {

@Id // see above
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@ManyToOne // many of these to one order
private Order order;

// pojo variables, getters and setters

private String product;

private double price;

private int quantity;

/**
* @return the order
*/
public Order getOrder() {
return order;
}

/**
* @return the product
*/
public String getProduct() {
return product;
}

/**
* @param product
* the product to set
*/
public void setProduct(String product) {
this.product = product;
}

/**
* @return the price
*/
public double getPrice() {
return price;
}

/**
* @param price
* the price to set
*/
public void setPrice(double price) {
this.price = price;
}

/**
* @return the quantity
*/
public int getQuantity() {
return quantity;
}

/**
* @param quantity
* the quantity to set
*/
public void setQuantity(int quantity) {
this.quantity = quantity;
}

/**
* @return the id
*/
public Long getId() {
return id;
}
}




So by a magic combination of scanning (to avoid coding the beans (and configuration?) in the spring beans file, a variety of annotations (Entity, manytoone, onetomany, id, generated value, and a hibernation configuration java class which uses the magic of reflection to set the properties, we have a full-blown hibernate system without mapping of specific fields.

Below is an example of how to utilize the data via some unit tests, aslo generated:


@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class OrderPersistenceTests {

@Autowired
private SessionFactory sessionFactory;

@Test
@Transactional
public void testSaveOrderWithItems() throws Exception {
Session session = sessionFactory.getCurrentSession();
Order order = new Order();
order.getItems().add(new Item());
session.save(order);
session.flush();
assertNotNull(order.getId());
}

@Test
@Transactional
public void testSaveAndGet() throws Exception {
Session session = sessionFactory.getCurrentSession();
Order order = new Order();
order.getItems().add(new Item());
session.save(order);
session.flush();
// Otherwise the query returns the existing order (and we didn't set the
// parent in the item)...
session.clear();
Order other = (Order) session.get(Order.class, order.getId());
assertEquals(1, other.getItems().size());
assertEquals(other, other.getItems().iterator().next().getOrder());
}

@Test
@Transactional
public void testSaveAndFind() throws Exception {
Session session = sessionFactory.getCurrentSession();
Order order = new Order();
Item item = new Item();
item.setProduct("foo");
order.getItems().add(item);
session.save(order);
session.flush();
// Otherwise the query returns the existing order (and we didn't set the
// parent in the item)...
session.clear();
Order other = (Order) session
.createQuery( "select o from Order o join o.items i where i.product=:product")
.setString("product", "foo").uniqueResult();
assertEquals(1, other.getItems().size());
assertEquals(other, other.getItems().iterator().next().getOrder());
}

}

No comments:

Post a Comment