Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Requery overview

Requery overview

Requery is lightweight ORM, alternatives of JPA

Avatar for Sunghyouk Bae

Sunghyouk Bae

July 19, 2018
Tweet

More Decks by Sunghyouk Bae

Other Decks in Programming

Transcript

  1. Agenda • Requery Overview • Why Requery • Requery Build

    process • Define Mapping • Usage EntityDataStore • EntityDataStore for Kotlin (Coroutines) • Introduction of Spring Data Requery
  2. Requery Overview • ORM Library for Java, Kotlin, Android •

    No Reflection (vs Hibernate proxy) • Typed Query language (vs Hibernate Criteria) • Upsert/Partial objects refresh • Compile time entity/query validation (vs Hibernate) • Entity is stateless (vs Hibernate stateful) • Thread 에 제한 받지 않음 (JPA EntityManager) • Support RxJava, Async Operations, Java 8
  3. Why Requery • Provide benefit of ORM • Entity Mapping

    • Schema Generation • Compile time error detecting • Performance • When bulk job, max 100x than JPA • REST API - 2~10x throughput • Support Upsert, Lazy loading …
  4. Requery Build Process - Java Define Entity Annotation Processing buildscript

    { repositories { jcenter() maven { url "https://plugins.gradle.org/m2/" } } dependencies { // for Java apt classpath "net.ltgt.gradle:gradle-apt-plugin:0.15" } } // lombokਸ gradle ীࢲ ࢎਊೞӝ ਤೠ plugin plugins { id 'io.franzbecker.gradle-lombok' version '1.14' } // lombokਸ gradle ীࢲ ࢎਊೞӝ ਤ೧ annotation processܳ ࢸ੿೧઱যঠ ೤פ׮. compileOnly "org.projectlombok:lombok" annotationProcessor "org.projectlombok:lombok" testAnnotationProcessor "org.projectlombok:lombok" annotationProcessor "io.requery:requery-processor" testAnnotationProcessor "io.requery:requery-processor" EntityDataStore<Object>
  5. Requery Build Process - Kotlin Define Entity Annotation Processing KotlinEntityDataStore<Any>

    // for kotlin entity kapt "io.requery:requery-processor" kaptTest "io.requery:requery-processor"
  6. Define Entity - Java @Getter @Entity(name = "BasicUser", copyable =

    true) @Table(name = "basic_user") public abstract class AbstractBasicUser extends AuditableLongEntity { @Key @Generated protected Long id; protected String name; protected String email; protected LocalDate birthday; protected Integer age; @ForeignKey @OneToOne protected AbstractBasicLocation address; @ManyToMany(mappedBy = "members") protected Set<AbstractBasicGroup> groups; @Column(unique = true) protected UUID uuid; @Override public int hashCode() { return Objects.hash(name, email, birthday); } @Transient @Override protected @NotNull ToStringBuilder buildStringHelper() { return super.buildStringHelper() .add("name", name) .add("email", email) .add("birthday", birthday); } private static final long serialVersionUID = -2693264826800934057L; }
  7. Define Entity - Kotlin @Entity(model = "functional") interface Person: Persistable

    { @get:Key @get:Generated val id: Long @get:Index(value = ["idx_person_name_email"]) var name: String @get:Index(value = ["idx_person_name_email", "idx_person_email"]) var email: String var birthday: LocalDate @get:Column(value = "'empty'") var description: String? @get:Nullable var age: Int? @get:ForeignKey @get:OneToOne(mappedBy = "person", cascade = [CascadeAction.DELETE, CascadeAction.SAVE]) var address: Address? @get:OneToMany(mappedBy = "owner", cascade = [CascadeAction.DELETE, CascadeAction.SAVE]) val phoneNumbers: MutableSet<Phone> @get:OneToMany val phoneNumberList: MutableList<Phone> @get:ManyToMany(mappedBy = "members") val groups: MutableResult<Group> @get:ManyToMany(mappedBy = "owners") val ownedGroups: MutableResult<Group> @get:ManyToMany(mappedBy = "id") @get:JunctionTable val friends: MutableSet<Person> @get:Lazy var about: String? @get:Column(unique = true) var uuid: UUID var homepage: URL var picture: String }
  8. EntityDataStore<Object> • findByKey • select / insert / update /

    upsert / delete • where / eq, lte, lt, gt, gte, like, in, not … • groupBy / having / limit / offset • support SQL Functions • count, sum, avg, upper, lower … • raw query
  9. @Test fun `insert user`() { val user = RandomData.randomUser() withDb(Models.DEFAULT)

    { insert(user) assertThat(user.id).isGreaterThan(0) val loaded = select(User::class) where (User::id eq user.id) limit 10 assertThat(loaded.get().first()).isEqualTo(user) } } val result = select(Location::class) .join(User::class).on(User::location eq Location::id) .where(User::id eq user.id) .orderBy(Location::city.desc()) .get() val result = raw(User::class, "SELECT * FROM Users") val rowCount = update(UserEntity::class) .set(UserEntity.ABOUT, "nothing") .set(UserEntity.AGE, 50) .where(UserEntity.AGE eq 100) .get() .value() val count = insert(PersonEntity::class, PersonEntity.NAME, PersonEntity.DESCRIPTION) .query(select(GroupEntity.NAME, GroupEntity.DESCRIPTION)) .get() .first() .count()
  10. CoroutineEntityDataStore val store = CoroutineEntityStore(this) runBlocking { val users =

    store.insert(RandomData.randomUsers(10)) users.await().forEach { user -> assertThat(user.id).isGreaterThan(0) } store .count(UserEntity::class) .get() .toDeferred() .await() .let { assertThat(it).isEqualTo(10) } } with(coroutineTemplate) { val user = randomUser() // can replace with `withContext { }` async { insert(user) }.await() assertThat(user.id).isNotNull() val group = RandomData.randomGroup() group.members.add(user) async { insert(group) }.await() assertThat(user.groups).hasSize(1) assertThat(group.members).hasSize(1) }
  11. spring-data-requery • RequeryOperations • Wrap EntityDataStore • RequeryTransactionManager for TransactionManager

    • Support Spring @Transactional • Better performance than spring-data-jpa • when exists, paging, not load all entities
  12. spring-data-requery • Repository built in SQL • ByPropertyName Auto generation

    methods • @Query for raw SQL Query • Query By Example • Not Supported • Association Path (not specified join method) • Named parameter in @Query (just use `?`)
  13. Setup spring-data-requery @Configuration @EnableTransactionManagement public class RequeryTestConfiguration extends AbstractRequeryConfiguration {

    @Override @Bean public EntityModel getEntityModel() { return Models.DEFAULT; } @Override public TableCreationMode getTableCreationMode() { return TableCreationMode.CREATE_NOT_EXISTS; } @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .build(); } }
  14. Provided Beans @Bean public io.requery.sql.Configuration requeryConfiguration() { return new ConfigurationBuilder(dataSource,

    getEntityModel()) // .useDefaultLogging() .setEntityCache(new EmptyEntityCache()) .setStatementCacheSize(1024) .setBatchUpdateSize(100) .addStatementListener(new LogbackListener()) .build(); } @Bean public EntityDataStore<Object> entityDataStore() { log.info("Create EntityDataStore instance."); return new EntityDataStore<>(requeryConfiguration()); } @Bean public RequeryOperations requeryOperations() { log.info("Create RequeryTemplate instance."); return new RequeryTemplate(entityDataStore(), requeryMappingContext()); } EntityCache ࢸ੿ Tip : ѐߊ दীח EmptyEntityCache, ਍৔ दীח Cache2kEntityCache ࢎਊ
  15. Use @Query in Repository interface DeclaredQueryRepository extends RequeryRepository<BasicUser, Long> {

    @Query("select * from basic_user u where u.email = ?") BasicUser findByAnnotatedQuery(String email); @Query("select * from basic_user u where u.email like ?") List<BasicUser> findAllByEmailMatches(String email); @Query("select * from basic_user u limit ?") List<BasicUser> findWithLimits(int limit); @Query("select * from basic_user u where u.name=? and u.email=? limit 1") BasicUser findAllBy(String name, String email); @Query("select u.id, u.name from basic_user u where u.email=?") List<Tuple> findAllIds(String email); @Query("select * from basic_user u where u.birthday = ?") List<BasicUser> findByBirthday(LocalDate birthday); }
  16. Query By Example BasicUser user = RandomData.randomUser(); user.setName("example"); requeryTemplate.insert(user); BasicUser

    exampleUser = new BasicUser(); exampleUser.setName("EXA"); ExampleMatcher matcher = matching() .withMatcher("name", startsWith().ignoreCase()) .withIgnoreNullValues(); Example<BasicUser> example = Example.of(exampleUser, matcher); Return<? extends Result<BasicUser>> query = buildQueryByExample(example); BasicUser foundUser = query.get().firstOrNull(); assertThat(foundUser).isNotNull().isEqualTo(user);
  17. Query by Property List<User> findByFirstnameOrLastname(String firstname, String lastname); List<User> findByLastnameLikeOrderByFirstnameDesc(String

    lastname); List<User> findByLastnameNotLike(String lastname); List<User> findByLastnameNot(String lastname); List<User> findByManagerLastname(String name); List<User> findByColleaguesLastname(String lastname); List<User> findByLastnameNotNull(); @Query("select u.lastname from SD_User u group by u.lastname") Page<String> findByLastnameGrouped(Pageable pageable); long countByLastname(String lastname); int countUsersByFirstname(String firstname); boolean existsByLastname(String lastname); Note: Association Path is not supported Note: Association Path is not supported
  18. Exists static class ExistsExecution extends JpaQueryExecution { @Override protected Object

    doExecute(AbstractJpaQuery query, Object[] values) { return !query.createQuery(values).getResultList().isEmpty(); } } static class ExistsExecution extends RequeryQueryExecution { @Override protected @Nullable Object doExecute(AbstractRequeryQuery query, Object[] values) { Result<?> result = (Result<?>) query.createQueryElement(values).limit(1).get(); return result.firstOrNull() != null; } } Spring Data JPA - ExistsExecution Spring Data Requery - ExistsExecution
  19. Delete in JPA static class DeleteExecution extends JpaQueryExecution { private

    final EntityManager em; public DeleteExecution(EntityManager em) { this.em = em; } @Override protected Object doExecute(AbstractJpaQuery jpaQuery, Object[] values) { Query query = jpaQuery.createQuery(values); List<?> resultList = query.getResultList(); for (Object o : resultList) { em.remove(o); } return jpaQuery.getQueryMethod().isCollectionQuery() ? resultList : resultList.size(); } } Spring Data JPA - DeleteExecution Load all entities to delete for cascading delete
  20. Delete in requery static class DeleteExecution extends RequeryQueryExecution { private

    final RequeryOperations operations; DeleteExecution(@NotNull RequeryOperations operations) { Assert.notNull(operations, "operations must not be null!"); this.operations = operations; } @SuppressWarnings("unchecked") @Override protected @Nullable Object doExecute(AbstractRequeryQuery query, Object[] values) { QueryElement<?> whereClause = query.createQueryElement(values); QueryElement<?> deleteQuery = applyWhereClause((QueryElement<?>) operations.delete(query.getDomainClass()), whereClause.getWhereElements()); Scalar<Integer> result = ((QueryElement<? extends Scalar<Integer>>) deleteQuery).get(); return result.value(); } } Spring Data Requery - DeleteExecution Use delete statements directly
 cascading depends on DB
  21. Contributors • Debop • Core module, Kotlin version • Diego

    • Unit testing, Benchmark, Examples • Jinie • Unit testing, Examples
  22. Q&A