spring-data-ebean

Ebean implementation for spring data.

Build Status Gitter chat GitHub release License

MAVEN Central Link

简体中文

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

Scenarios implemented

  1. Fetch single entity based on primary key
  2. Fetch list of entities based on condition
  3. Save new single entity and return primary key
  4. Batch insert multiple entities of the same type and return generated keys
  5. Update single existing entity - update all fields of entity at once
  6. Fetch many-to-one relation (Company for Department)
  7. Fetch one-to-many relation (Departments for Company)
  8. Update entities one-to-many relation (Departments in Company) - add two items, update two items and delete one item - all at once
  9. Complex select - construct select where conditions based on some boolean conditions + throw in some JOINs
  10. Execute query using JDBC simple Statement (not PreparedStatement)
  11. Remove single entity based on primary key

Why choose Ebean ORM

Conditions on frameworks which I choose for consideration:

  1. The framework should embrace - not hide - SQL language and RDBMS we are using
  2. The framework can implement DDD
  3. Can utilize JPA annotations, but must not be full JPA implementation
  4. 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

jOOQ

MyBatis

EBean

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

  1. 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;
}
  1. 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);
}
  1. 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>
  1. 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());
    }

}