Traditionally Java and databases have been on friends. Starting with the java persistence libraries where soon enough we could use prepared statements to secure the way we queried the database. After that the world of Java was shocked with the birth of ORM toolings. These babies really decoupled the whole data tier from the rest and saved a lot of work. Implementations of JPA and Eclipse (Top) link helped the word easy up the way of persistence. That the future is allways one step away is common knowledge these days. But in some cases the future allready catched up with me before I realize this. m
In the case of JPA repositories that happened. I figured this technique out the last couple of weeks and I am already in love with it. They pushed things a little bit further then my imagination would go. It is not Star trek yet but still quite impressive.
The concept:
The Idea behind this to diminish the boiler plate coding further. The benefits of this I can bring down to two points:
- After configuring this right which is a bit more work, the coding comes down to allmost nothing.
- Because of the fact that the persistence part is mostly configuration, the repositories them selves can (not necessarily) function as a complete database tier.
The configuration:
The configuration I set up is a mix of spring and maven:
Maven pom part:
<properties>
<spring.version>4.0.3.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>com.jolbox</groupId>
<artifactId>bonecp</artifactId>
<version>0.8.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
<!-- Spring Data JPA -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.5.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.3.4.Final</version>
</dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.0-801.jdbc4</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.3.5.Final</version>
//could not load the JPA from here so I did it separate.
<exclusions>
<exclusion>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
</exclusion>
</exclusions>
</dependency>
//The jpa libary that is needed for hibernate.
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
<dependency>
the persistence xml which needs to be placed in src/main/resources/META-INF.
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="dataSource" transaction-type="RESOURCE_LOCAL">
<description><YOUR DESCRIPTION></description>
<class>com.shops.data.info.CustomerInfo</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver" />
<property name="javax.persistence.jdbc.url" value="jdbc:postgresql://localhost:5432/shops" />
<property name="javax.persistence.jdbc.user" value="<USERNAME>" />
<property name="javax.persistence.jdbc.password" value="<PASSWORD>" />
//optional parameters.
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.hbm2ddl.auto" value="create" />
</properties>
</persistence-unit>
</persistence>
The spring context file:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<jpa:repositories base-package="THE REPOSITORY PACKAGE"/>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.postgresql.Driver"/>
<property name="url" value="jdbc:postgresql://localhost:5432/<YOURDATABASE>"/>
<property name="username" value="<USERNAME>"/>
<property name="password" value="PASSWORD"/>
</bean>
<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true"/>
<property name="generateDdl" value="true"/>
<property name="database" value="POSTGRESQL"/>
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
<!-- spring based scanning for entity classes-->
<property name="packagesToScan" value="<THE D.O. PACKAGE>"/>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"/>
</beans>
The repository part:
You can have multiple repositories. How to deal with the number of repositories depends completely on the fact how you want to deal delete with your data.
Here is al single example:
import com.my.web.shop.dataobjects.CustomerInfo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import javax.transaction.Transactional;
import java.util.List;
@Repository
@Transactional
interface CustomerRepository extends JpaRepository<CustomerInfo, Long > {
public List<CustomerInfo> findByEmailadressAndPassword(String email, String password);
public List<CustomerInfo> findByEmailadress(String email);
}
In this litle piece of code you find some astonishing things first of all after creating this interface, I did not create an implementation. Creating your own repository is not necessary in the most cases. As a matter of fact if you create an Repository on your own, you be at the point where we would start writing this extra boiler plate code we done for years.
Under water this interface is hooked up with the CrudRepository of Spring. This baby gives you allmost everything you need. All the basic database actions as select, update, delete and insert are there.
The second part you might find different is the naming of the methods. This is actually one of the most amazing parts. In the name of the method you declare the fields which you want to use in your sql statement. The parameters represent the values. So the first method would build under water: where emailAdress = "a@b.nl" and password = "myPassword.". If you have a big query the name of your method would become very long. Luckily there is another way to deal with queries. The @Query annotation helps to solve this problem. It could look something like this:
//The D.O. for the myShop customers.
@Entity
@Table(name = "customers")
public class CustomerInfo implements Serializable{
@Id
@GeneratedValue
private long id;
@Column
private String emailadress;
@Column
private String password;
@Id
public Long getId() {
return id;
}
public String getPassword() {
return password;
}
public void setId(Long id) {
this.id = id;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmailadress() {
return emailadress;
}
public void setEmailadress(String emailadress) {
this.emailadress = emailadress;
}
}
// The D.O that couples that couples the customer to the test data. And contains the test data.
@Entity
@Table(name = "test")
public class Test implements Serializable {
@Id
@GeneratedValue
private long id;
@Column
private String label;
@Column(name = "customer_id")
private long customersId;
@OneToMany()
@JoinColumn(name="id")
private List<CustomerInfo> customerInfo;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public long getCustomersId() {
return customersId;
}
public void setCustomersId(long customersId) {
this.customersId = customersId;
}
}
//The test data repository
import com.shops.data.info.Test;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import javax.transaction.Transactional;
import java.util.List;
/**
* Created by compurat on 4/19/14.
*/
@Repository
@Transactional
interface TestRepository extends JpaRepository<Test, Long > {
@Query("SELECT t.label FROM Test t left join t.customerInfo c where t.id=(?1)")
public String findById(long id);
}
As you can see the findById method is not acting on the method name and the parameter value but will act on the @Query that we put on top of it. The last part of the query works in a simular way as we know with PreparedStatement. The questionmark tells the compiler this a placeholder an d the number tells which parameter in line it needs to pickup. In this case we only have one parameter. Not enough space for a mistake :).
There is another piece that might have caught your eye. There is a join in this query. It took me a while to figure out how that worked. The secret lies in the simple fact that you have to divide the join over two places.
- @OneToMany()@JoinColumn(name="id")private List<CustomerInfo> customerInfo; defines this part of the join. It tells: listen there is a join coming up. In this case it is a OneToMany but it can also be @ManyToMany or @OneToOne. The @JoinColumn tells to connect to a certainfield in the CustomerInfo class.
- The left join t.customerInfo c in the @Query part tells to actually execute the join. As you might notice the ON part from sql is not translated here. That is one of the parts That still puzzles me.How is it known to couple to? For the rest As allways as you understand the concept You find out it was not as hard as you expected first.
Conclusion:
The concept itself and the amazement it gave me leveled up the steepness for me to understand it. But now I am happy I gave it time and picked up the things I allready know now. The concept to me is still a bit futuristic. But the fact it is here and I love to use it. The power of this is amazing.
Have fun!
Geen opmerkingen:
Een reactie posten