Table of contents
Introduction
Sometimes a project requires that you store additional information in the database. This usually requires the creation of a table, and the creation of classes to manage this table. Currently, the best and recommended way to solve such a problem is to create a class representing a table in the database, and map this class using the Hibernate library. In general, we can implement the mapping through a .hbm.xml mapping file or through an annotation. It is recommended to use annotation due to the fact that we do not have to change the system file hibernate.cfg.xml. The operations performed on the created objects are typically adding, deleting, modifying and searching therefore, to eliminate the need to implement methods to enable the above-mentioned operations, several mechanisms are available in the API to facilitate working on custom database objects. In this chapter, I will present the available ways to manage your own database objects.
Example of mapping
Suppose a customer wants to store information about phones in his company. We will create an example Phone class, which we will use in the following examples. The class also includes mappings.
@Entity @Table( name = "pm_phone" ) @SequenceGenerator( name = "pm_phone_id_seq", sequenceName = "pm_phone_id_seq" ) public class Phone { private Long id; private String number; private User owner; @Id @GeneratedValue( strategy = GenerationType.AUTO, generator = "pm_phone_id_seq" ) public Long getId() { return id; } public void setId( Long id ) { this.id = id; } public String getNumber() { return number; } public void setNumber( String number ) { this.number = number; } @ManyToOne( fetch = FetchType.LAZY ) @JoinColumn( name = "userid" ) public User getOwner() { return owner; } public void setOwner( User owner ) { this.owner = owner; } }
The @Entity annotation means that the class will be mapped to a table in the database and will be scanned by Hibernate during system startup. In @Table we specify the name of the table(see Conventions). The @SequenceGenerator annotation allows you to create a sequencer. The name parameter must be unique within the entire system, so it should be set the same as sequenceName(the name of the sequencer in the database). At least one field marked @Id is required in each mapped class, which will become the primary key of the relationship. The @GeneratedValue allows you to bind a previously created sequencer to a field(the binding is done using the name paremeter, not sequenceName).
Conventions
In PlusWorkflow, each newly created table should have the prefix 'pm_'. Also remember that the table and seqencer name should not be too long(30 characters max), as some databases do not allow long table names. Next we can define additional mapping options. In the example, we have a link to the usertable table via the owner field. The number field will also be mapped(a column will be created in the pm_phone table). If we want to omit a field then we use @Transient.
Attention
Any class marked @Entity must be in the com.suncode.pwfl package tree
CustomService
The easiest and fastest way to manage database objects is to use the CustomService class. Let's look at an example:
CustomService<Phone, Long> ps = ServiceFactory.getCustomService( Phone.class, Long.class ); Phone p = new Phone(); p.setNumber( "777 777 777" ); ps.save( p ); List<Phone> phones = ps.getAll(); for ( Phone phone : phones ) { log.debug( phone.getNumber() ); }
First, we need to download the appropriate service, as parameters we transfer our class and the class of the field of which it is the primary key. The service class created in this way provides many useful functions to manage our object. In the example, we save the new phone in the database, and then load the list of all phones from the pm_phone table.
Service (advanced)
If we have a more complex problem and additional actions should be called during database operations or the execution of operations on our object is more complex then we can create our own Service class. In order to create the Service class, we need to build the whole mechanism on which consists of two layers - DAO and Service. The whole thing will contain two interfaces and two implementation classes. Let's start with the DAO layer
public interface PhoneDao extends EditableDao<Phone, Long> { public void customMethod(); }
@Repository( "phoneDao" ) public class PhoneDaoImpl extends HibernateEditableDao<Phone, Long> implements PhoneDao { public void customMethod() { getSession().createSQLQuery( "..." ); } }
The important thing here is that in the DAO layer we do not deal with transactions, i.e. when calling a DAO function, the transaction must be started (that's why we need a Service class). In DAO we can add our own functions that perform some specific database operations (we do not implement business logic, validation, etc. here). From the superclass we get many useful methods, and as you can see above we can retrieve a session. If our service is to allow only read operations we can use BaseDao and HibernateBaseDao instead of EditableDao and HibernateEditableDao respectively.
Service Layer:
public interface PhoneService extends EditableService<Phone, Long> { }
@Service( "phoneService" ) public class PhoneServiceImpl extends EditableServiceImpl<Phone, Long, PhoneDao> implements PhoneService { @Autowired public void setDao( PhoneDao dao ) { this.dao = dao; } @Override @Transactional public Long save( Phone phone ) { dao.customMethod();//both methods are performed in a single transaction super.save(phone); } }
It is always mandatory for us to write a setDao method, which, combined with the @Autowired annotation, will load the appropriate implementation into our dao object. Remember to add the @Transactional annotation to the functions where we perform database operations.
Where the type Long is used in the DAO and in the Service it is because that is the type of the field that is the Id in the model (here the Phone class). If the type of the field which is the Id will be different then, similarly, you also need to use a different type in DAO and Service.
@Autowired PhoneService ps;//works only in classes managed by the application context PhoneService ps=SpringContext.getBean("phoneService"); PhoneService ps=SpringContext.getBean(PhoneService.class);
Attention
If there are several classes in the system marked with the same name, for example. @Service( "phoneService" ) then the system will not know which implementation to match therefore only the last of the above will work.
PhoneService ps=SpringContext.getBean(PhoneService.class); Phone p = new Phone(); p.setNumber( "777 777 777" ); ps.save( p ); List<Phone> phones = ps.getAll(); for ( Phone phone : phones ) { log.debug( phone.getNumber() ); }
Useful resources: