Slide 1

Slide 1 text

Requery Coupang Catalog Platform & Quality [email protected]

Slide 2

Slide 2 text

Agenda • Requery Overview • Why Requery • Requery Build process • Define Mapping • Usage EntityDataStore • EntityDataStore for Kotlin (Coroutines) • Introduction of Spring Data Requery

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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 …

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Requery Build Process - Kotlin Define Entity Annotation Processing KotlinEntityDataStore // for kotlin entity kapt "io.requery:requery-processor" kaptTest "io.requery:requery-processor"

Slide 7

Slide 7 text

IntelliJ IDEA Settings IDEA ࢚ীࢲ పझ౟ ੘স द ੗زਵ۽ apt taskܳ ݢ੷ ࣻ೯೧ ળ׮.

Slide 8

Slide 8 text

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 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; }

Slide 9

Slide 9 text

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 @get:OneToMany val phoneNumberList: MutableList @get:ManyToMany(mappedBy = "members") val groups: MutableResult @get:ManyToMany(mappedBy = "owners") val ownedGroups: MutableResult @get:ManyToMany(mappedBy = "id") @get:JunctionTable val friends: MutableSet @get:Lazy var about: String? @get:Column(unique = true) var uuid: UUID var homepage: URL var picture: String }

Slide 10

Slide 10 text

EntityDataStore • 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

Slide 11

Slide 11 text

@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()

Slide 12

Slide 12 text

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) }

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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 `?`)

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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 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 ࢎਊ

Slide 17

Slide 17 text

Use @Query in Repository interface DeclaredQueryRepository extends RequeryRepository { @Query("select * from basic_user u where u.email = ?") BasicUser findByAnnotatedQuery(String email); @Query("select * from basic_user u where u.email like ?") List findAllByEmailMatches(String email); @Query("select * from basic_user u limit ?") List 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 findAllIds(String email); @Query("select * from basic_user u where u.birthday = ?") List findByBirthday(LocalDate birthday); }

Slide 18

Slide 18 text

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 example = Example.of(exampleUser, matcher); Return> query = buildQueryByExample(example); BasicUser foundUser = query.get().firstOrNull(); assertThat(foundUser).isNotNull().isEqualTo(user);

Slide 19

Slide 19 text

Query by Property List findByFirstnameOrLastname(String firstname, String lastname); List findByLastnameLikeOrderByFirstnameDesc(String lastname); List findByLastnameNotLike(String lastname); List findByLastnameNot(String lastname); List findByManagerLastname(String name); List findByColleaguesLastname(String lastname); List findByLastnameNotNull(); @Query("select u.lastname from SD_User u group by u.lastname") Page 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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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 result = ((QueryElement>) deleteQuery).get(); return result.value(); } } Spring Data Requery - DeleteExecution Use delete statements directly
 cascading depends on DB

Slide 23

Slide 23 text

Contributors • Debop • Core module, Kotlin version • Diego • Unit testing, Benchmark, Examples • Jinie • Unit testing, Examples

Slide 24

Slide 24 text

Resources • requery.io • kotlinx-data-requery in coupang gitlab • spring-data-requery in coupang gitlab

Slide 25

Slide 25 text

Q&A

Slide 26

Slide 26 text

Thank you!