spring-data-ebean
Ebean implementation for spring data.
The primary goal of the Spring Data project is to make it easier to build Spring-powered applications that use data access technologies. This module deals with enhanced support for Ebean ORM based data access layers.
Features
- Ebean implementation of CRUD methods for JPA Entities
- Dynamic query generation from query method names and annotation
- Support query channel service
- Transparent triggering of Ebean Query by query methods
- Implementation domain base classes providing basic properties
- Support for transparent auditing (created, last changed)
- Possibility to integrate custom repository code
- Easy Spring integration with custom namespace
Scenarios implemented
- Fetch single entity based on primary key
- Fetch list of entities based on condition
- Save new single entity and return primary key
- Batch insert multiple entities of the same type and return generated keys
- Update single existing entity - update all fields of entity at once
- Fetch many-to-one relation (Company for Department)
- Fetch one-to-many relation (Departments for Company)
- Update entities one-to-many relation (Departments in Company) - add two items, update two items and delete one item - all at once
- Complex select - construct select where conditions based on some boolean conditions + throw in some JOINs
- Execute query using JDBC simple Statement (not PreparedStatement)
- Remove single entity based on primary key
Why choose Ebean ORM
Conditions on frameworks which I choose for consideration:
- The framework should embrace - not hide - SQL language and RDBMS we are using
- The framework can implement DDD
- Can utilize JPA annotations, but must not be full JPA implementation
- The framework must be mature enough for "enterprise level" use
Subjective pros/cons of each framework
Reference:java-persistence-frameworks-comparison
Hibernate/JPA
JDBC Template
- Pros
- Feels like you are very close to JDBC itself
- Implemented all of the scenarios without bigger issues - there were no hidden surprises
- Very easy batch operations
- Easy setup
- Cons
- Methods in JDBCDataRepositoryImpl are not much readable - that's because you have to inline SQL in Java code. It would have been better if Java supported multiline strings.
- Debug logging could be better
jOOQ
- Pros
- Very fluent, very easy to write new queries, code is very readable
- Once setup it's very easy to use, excellent for simple queries
- Awesome logger debug output
- Cons
- Paid license for certain databases - it'll be difficult to persuade managers that it's worth it :)
- Not so much usable for big queries - it's better to use native SQL (see scenario 9.)
- Weird syntax of batch operations (in case that you do not use UpdatableRecord). But it's not a big deal...
MyBatis
- Pros
- Writing SQL statements in XML mapper file feels good - it's easy to work with parameters
- Cons
- Quite a lot of files for single DAO implementation, DAO/Repository and XXXMapper and XXXMapper.xml
- Can't run batch and non-batch operations in single SqlSession
- Too many XML mapper files.
- Can't implement DDD
EBean
- Pros
- Everything looks very nice - all the scenarios are implemented by very readable code
- Super simple batch operations (actually it's only about using right method :) )
- Although there are methods which make CRUD operations and Querying super simple, there are still means how to execute plain SQL and even a way how to get the basic JDBC Transaction object, which you can use for core JDBC stuff. That is really good.
- Cons
- DTO query do not support XML mapping
- Necessity of "enhancement" of the entities - this was quite surprising to me - but actually it's basically only about right environment setup (IDE plugin and Gradle plugin) and then you don't have to think about it
Quick Start
Create maven project,recommend to use spring boot and spring-data-ebean-spring-boot to build web project.
Examples: spring-boot-data-ebean-samples
- Create modal as table entity or sql entity or DTO:
Table entity:
@Entity
@Getter
@Setter
public class User {
@Id
@GeneratedValue
private Integer id;
private String firstname;
private String lastname;
@Column(nullable = false, unique = true) private String emailAddress;
}
Sql entity:
Sql entity:
@Entity
@Sql
@Getter
@Setter
public class UserInfo {
private String firstName;
private String lastName;
private String emailAddress;
}
POJO DTO:
@Getter
@Setter
public class UserDTO {
private String firstName;
private String lastName;
private String emailAddress;
}
- Create a repository interface:
public interface UserRepository extends EbeanRepository<User, Long> {
@Query("where emailAddress = :emailAddress order by id desc")
User findUserByEmailAddressEqualsOql(@Param("emailAddress") String emailAddress);
@Query("select (firstname,lastname,address) fetch manager (lastname) where lastname = :lastname order by id desc")
List<User> findByLastnameOql(@Param("lastname") String lastname);
@Query(nativeQuery = true, value = "select * from user where email_address = :emailAddress order by id desc")
User findUserByEmailAddressEquals(@Param("emailAddress") String emailAddress);
@Query(nativeQuery = true, value = "select * from user where lastname = :lastname order by id desc")
List<User> findUsersByLastnameEquals(@Param("lastname") String lastname);
@Query(nativeQuery = true, value = "update user set email_address = :newEmail where email_address = :oldEmail")
@Modifying
int changeUserEmailAddress(@Param("oldEmail") String oldEmail, @Param("newEmail") String newEmail);
@Query("delete from user where emailAddress = :emailAddress")
@Modifying
int deleteUserByEmailAddressOql(@Param("emailAddress") String emailAddress);
@Query(nativeQuery = true, value = "delete from user where email_address = :emailAddress")
@Modifying
int deleteUserByEmailAddress(@Param("emailAddress") String emailAddress);
@Query(name = "withManagerById")
List<User> findByLastnameNamedOql(@Param("lastname") String lastname);
List<User> findAllByEmailAddressAndLastname(@Param("emailAddress") String emailAddress, @Param("lastname") String lastname);
}
- Options to create a named query config in xml when using named query:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ebean xmlns="http://ebean-orm.github.io/xml/ns/ebean">
<entity class="org.springframework.data.ebean.sample.domain.User">
<named-query name="withManagerById">
<query>
select (firstname,lastname,address)
fetch manager (lastname)
where lastname = :lastname order by id desc
</query>
</named-query>
<raw-sql name="byEmailAddressEquals">
<query>
select * from user
where email_address = :emailAddress
order by id desc
</query>
</raw-sql>
</entity>
<entity class="org.springframework.data.ebean.sample.domain.UserInfo">
<raw-sql name="userInfo">
<query>
select first_name, last_name, email_address from user
</query>
</raw-sql>
<raw-sql name="userInfoByEmail">
<query>
select first_name, last_name, email_address from user
where email_address = :emailAddress
order by id desc
</query>
</raw-sql>
</entity>
</ebean>
- Write your code to use model and repository(FOR DDD CURD) or
EbeanQueryChannelService
(FOR DTO QUERY):
UserRepositoryIntegrationTests.java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SampleConfig.class)
public class UserRepositoryIntegrationTests {
@Autowired
UserRepository repository;
// Test fixture
User user;
@Before
public void setUp() throws Exception {
SimpleGuavaDomainEventPublisher.getInstance().register(new Object() {
@Subscribe
public void lister(UserEmailChangedEvent userEmailChangedEvent) {
System.out.println(userEmailChangedEvent.toString());
}
});
repository.deleteAll();
user = new User("Xuegui", "Yuan", "[email protected]");
user.setAge(29);
user = repository.save(user);
}
@Test
public void sampleTestCase() {
// test find all orm query
List<User> result1 = (List<User>) repository.findAll();
result1.forEach(it -> System.out.println(it));
assertEquals(1, result1.size());
assertEquals("Yuan", result1.get(0).getFullName().getLastName());
assertThat(result1, hasItem(user));
// test find list orm query
List<User> result2 = repository.findByLastnameOql("Yuan");
assertEquals(1, result2.size());
assertEquals("Yuan", result2.get(0).getFullName().getLastName());
assertThat(result2, hasItem(user));
// test find list sql query
List<User> result3 = repository.findUsersByLastNameEquals("Yuan");
assertEquals(1, result3.size());
assertEquals("Yuan", result3.get(0).getFullName().getLastName());
// test find one orm query
User result4 = repository.findUserByEmailAddressEqualsOql("[email protected]");
assertEquals("[email protected]", result4.getEmailAddress());
// test find one sql query
User result5 = repository.findUserByEmailAddressEquals("[email protected]");
assertEquals("[email protected]", result5.getEmailAddress());
// test update orm query
int result6 = repository.changeUserEmailAddress("[email protected]", "[email protected]");
assertEquals(1, result6);
// test find list orm query
List<User> result7 = repository.findByLastnameOql("Yuan");
assertEquals("[email protected]", result7.get(0).getEmailAddress());
// test delete sql query
int result8 = repository.deleteUserByEmailAddress("[email protected]");
assertEquals(1, result8);
// test find one sql query
User result9 = repository.findUserByEmailAddressEquals("[email protected]");
assertNull(result9);
// test create
user = new User("Xuegui", "Yuan", "[email protected]");
user.setAge(29);
user = repository.save(user);
// test find list named orm query
List<User> result10 = repository.findByLastNameNamedOql("Yuan");
assertEquals(1, result10.size());
assertEquals("Yuan", result10.get(0).getFullName().getLastName());
// test find one orm query
User result11 = repository.findUserByEmailAddressEquals("[email protected]");
assertNotNull(result11);
// test delete orm update
int result12 = repository.deleteUserByEmailAddressOql("[email protected]");
assertEquals(1, result12);
// test find one sql query
User result13 = repository.findUserByEmailAddressEquals("[email protected]");
assertNull(result13);
}
@Test
public void testFindByMethodName() {
List<User> result1 = repository.findAllByEmailAddressAndFullNameLastName("[email protected]", "Yuan");
assertEquals(1, result1.size());
assertEquals("Yuan", result1.get(0).getFullName().getLastName());
assertThat(result1, hasItem(user));
}
@Test
public void testFindByExample() {
User u = new User();
u.setEmailAddress("YUANXUEGUI");
List<User> result1 = repository.findAll(Example.of(u, ExampleMatcher.matchingAll()
.withIgnoreCase(true)
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING)));
assertEquals(1, result1.size());
assertEquals("Yuan", result1.get(0).getFullName().getLastName());
assertThat(result1, hasItem(user));
List<User> result2 = repository.findAll(Example.of(u, ExampleMatcher.matchingAll()
.withIgnoreCase(false)
.withStringMatcher(ExampleMatcher.StringMatcher.EXACT)));
assertEquals(0, result2.size());
}
@Test
public void testAuditable() {
User u = repository.findUserByEmailAddressEqualsOql("[email protected]");
assertEquals("test", u.getCreatedBy());
assertEquals("test", u.getLastModifiedBy());
}
@Test
public void testDomainEvent() {
user.changeEmail("[email protected]");
repository.save(user);
User u = repository.findOneByProperty("emailAddress", "[email protected]");
assertNotNull(u);
assertEquals("[email protected]", u.getEmailAddress());
}
}
QueryObject UserQuery
@Data
@IncludeFields("emailAddress,fullName(lastName,firstName),age")
public class UserQuery {
@ExprParam(expr = ExprType.CONTAINS)
private String emailAddress;
@ExprParam(name = "age", expr = ExprType.GE)
private int ageStart;
@ExprParam(name = "age", expr = ExprType.LE)
private int ageEnd;
}
EbeanQueryChannelServiceIntegrationTests.java
package org.springframework.data.ebean.querychannel;
import io.ebean.Query;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.ebean.sample.config.SampleConfig;
import org.springframework.data.ebean.sample.domain.User;
import org.springframework.data.ebean.sample.domain.UserInfo;
import org.springframework.data.ebean.sample.domain.UserRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
/**
* @author Xuegui Yuan
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SampleConfig.class)
public class EbeanQueryChannelServiceIntegrationTests {
// Test fixture
User user;
@Autowired
private EbeanQueryChannelService ebeanQueryChannelService;
@Autowired
private UserRepository repository;
@Before
public void initUser() {
repository.deleteAll();
user = new User("QueryChannel", "Test", "[email protected]");
user.setAge(29);
user = repository.save(user);
}
@Test
public void createSqlQueryMappingColumns() {
String sql1 = "select first_name, last_name, email_address from user where last_name= :lastName";
String sql2 = "select first_name as firstName, last_name as lastName, email_address as emailAddress from user where last_name= :lastName";
Map<String, String> columnsMapping = new HashMap<>();
columnsMapping.put("first_name", "firstName");
columnsMapping.put("last_name", "lastName");
Query<UserInfo> query1 = ebeanQueryChannelService.createSqlQuery(UserInfo.class,
sql1);
Query<UserInfo> query2 = ebeanQueryChannelService.createSqlQuery(UserInfo.class,
sql2);
Query<UserInfo> query3 = ebeanQueryChannelService.createSqlQueryMappingColumns(UserInfo.class,
sql1, columnsMapping);
query1.setParameter("lastName", "Test");
query2.setParameter("lastName", "Test");
query3.setParameter("lastName", "Test");
UserInfo userInfo1 = query1.findOne();
UserInfo userInfo2 = query2.findOne();
UserInfo userInfo3 = query3.findOne();
assertEquals("QueryChannel", userInfo1.getFirstName());
assertEquals("[email protected]", userInfo1.getEmailAddress());
assertEquals("QueryChannel", userInfo2.getFirstName());
assertEquals("[email protected]", userInfo2.getEmailAddress());
assertEquals("QueryChannel", userInfo3.getFirstName());
assertEquals("[email protected]", userInfo3.getEmailAddress());
}
@Test
public void createNamedQuery() {
UserInfo userInfo = ebeanQueryChannelService.createNamedQuery(UserInfo.class,
"userInfoByEmail").setParameter("emailAddress",
"[email protected]").findOne();
assertEquals("QueryChannel", userInfo.getFirstName());
assertEquals("[email protected]", userInfo.getEmailAddress());
}
@Test
public void createNamedQueryWhere() {
UserInfo userInfo = ebeanQueryChannelService.createNamedQuery(UserInfo.class,
"userInfo").where()
.eq("emailAddress", "[email protected]").findOne();
assertEquals("QueryChannel", userInfo.getFirstName());
assertEquals("[email protected]", userInfo.getEmailAddress());
}
@Test
public void createDtoQuery() {
String sql = "select first_name, last_name, email_address from user where email_address = :emailAddress";
UserDTO userDTO = ebeanQueryChannelService.createDtoQuery(UserDTO.class, sql)
.setParameter("emailAddress", "[email protected]")
.findOne();
assertEquals("QueryChannel", userDTO.getFirstName());
assertEquals("[email protected]", userDTO.getEmailAddress());
}
@Test
public void query_queryObject() {
UserQuery userQuery = new UserQuery();
userQuery.setEmailAddress("[email protected]");
userQuery.setAgeStart(1);
userQuery.setAgeEnd(30);
UserDTO user = queryChannel.createQuery(User.class, userQuery)
.asDto(UserDTO.class)
.setRelaxedMode()
.findOne();
assertEquals("[email protected]", user.getEmailAddress());
}
@Test
public void applyQueryObject() {
UserQuery userQuery = new UserQuery();
userQuery.setEmailAddress("[email protected]");
userQuery.setAgeStart(1);
userQuery.setAgeEnd(30);
UserInfo userInfo = EbeanQueryChannelService.applyWhere(queryChannel.createNamedQuery(UserInfo.class,
"userInfo").where(), userQuery).findOne();
assertEquals("QueryChannel", userInfo.getFirstName());
assertEquals("[email protected]", userInfo.getEmailAddress());
}
}