Spring Boot with MyBatis KESTI 개발팀
Spring Boot with MyBatis • mybatis-spring-boot-starter 소개 • MyBatis Tutorials • setup • mappers • testing • Bonus Tutorials • Flyway • QueryDSL 2016-10-12KESTI 개발팀 세미나 2
MyBatis for Spring Boot • MyBatis 를 Spring Boot Application 에서 사용하기 위한 라이브러리 • 홈페이지 : http://www.mybatis.org/mybatis-spring-boot/ • 소스 : https://github.com/mybatis/mybatis-spring-boot • 예제 : • KESTI SpringBoot-MyBatis Tutorials • Spring Boot 에서 Java Config를 통해 myBatis 연동하기 2016-10-12KESTI 개발팀 세미나 3
MyBatis Tutorials 2016-10-12KESTI 개발팀 세미나 4
Tutorial Overview 1. mybatis-spring-boot-starter 프로젝트 구성 2. Mapper – Annotation / XML 방식 3. MyBatis 설정 방법 4. Flyway 를 이용한 database migration 방법 5. MyBatis 단위 테스트 2016-10-12KESTI 개발팀 세미나 5
1. 프로젝트 구성 <dependencies> <dependency> <groupId>kr.kesti.kesti4j</groupId> <artifactId>kesti4j-data-java6</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.0.1</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> </dependencies> 2016-10-12KESTI 개발팀 세미나 6
Project Structures configuration data handling source Spring Boot Application Flyway database migration MyBatis configuration & XML Mappers 환경설정 정보 Test 코드 2016-10-12KESTI 개발팀 세미나 7
XML Mapper <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="kr.kesti.mybatis.examples.domain.models"> <select id="selectActorByFirstname" parameterType="String" resultType="Actor"> SELECT * FROM Actors WHERE firstname = #{firstname} limit 1 </select> <insert id="insertActor" useGeneratedKeys="true" keyProperty="id"> insert into Actors(firstname, lastname) values( #{firstname}, #{lastname} ) </insert> <!-- Oracle, PostgreSQL 은 SEQUENCE --> <!-- <insert id="insertActorBySequence"> <selectKey keyProperty="id" resultType="int" order="BEFORE" statementType="PREPARED"> SELECT SEQ_ID.nextval FROM DUAL </selectKey> INSERT INTO Autors (id, firstname, lastname) VALUES (#{id}, #{firstname}, #{lastname}) </insert> --> </mapper> 2016-10-12KESTI 개발팀 세미나 8
Annotated Mapper /** * Actor 를 위한 MyBatis Mapper 입니다. */ public interface ActorMapper { @Select("SELECT * FROM Actors WHERE firstname = #{firstname}") Actor findByFirstname(@Param("firstname") String firstname); @Select("SELECT * FROM Actors") List<Actor> findAll(); @Delete("DELETE FROM Actors WHERE id=#{id}") void deleteById(@Param("id") Integer id); } /** * MyBatis 를 Spring boot 에서 사용하기 위한 환경설정 */ @Configuration @EnableAutoConfiguration @ComponentScan(basePackageClasses = { ActorRepository.class }) @MapperScan(basePackageClasses = { ActorMapper.class }) public class MyBatisConfiguration extends AbstractFlywayMyBatisConfiguration { 2016-10-12KESTI 개발팀 세미나 9 XML Mapper 보다 좋은점은?
Repository / DAO /** * {@link Actor} 를 위한 Repository (DAO) 입니다. */ @Slf4j @Repository public class ActorRepository { @Autowired ActorMapper mapper; @Autowired SqlSessionTemplate session; public Actor findByFirstname(@NonNull String firstname) { return mapper.findByFirstname(firstname); } public Actor findByFirstnameWithXmlMapper(@NonNull String firstname) { return session.selectOne("selectActorByFirstname", firstname); } public List<Actor> findAll() { return mapper.findAll(); } public int insertActor(@NonNull Actor actor) { return session.insert("insertActor", actor); } public void deleteById(@NonNull Integer id) { mapper.deleteById(id); } } 2016-10-12KESTI 개발팀 세미나 10
MyBatis JavaConfig @Configuration @EnableAutoConfiguration @ComponentScan(basePackageClasses = { ActorRepository.class }) @MapperScan(basePackageClasses = { ActorMapper.class }) public class MyBatisConfiguration extends AbstractFlywayMyBatisConfiguration { // datasource 용 properties (application.properties 에서 자동으로 정보를 읽어옴) @Inject DataSourceProperties dataSourceProperties; // mybatis 용 properties (application.properties 에서 자동으로 정보를 읽어옴) @Inject MybatisProperties mybatisProperties; @Override protected DatabaseSetting getDatabaseSetting() { return DatabaseSetting.builder() .driverClass(dataSourceProperties.getDriverClassName()) .jdbcUrl(dataSourceProperties.getUrl()) .build(); } @Override protected String getMyBatisConfigPath() { return mybatisProperties.getConfig(); } @Override protected String getMyBatisMapperPath() { return mybatisProperties.getMapperLocations()[0]; } } 2016-10-12KESTI 개발팀 세미나 11 XML Config 와 장단점 비교
Spring Boot application.properties ### DataSource spring.datasource.driver-class-name=org.h2.Driver spring.datasource.jdbc-url=jdbc:h2:mem spring.datasource.username=sa spring.datasource.password= ### MyBatis mybatis.config=mybatis/mybatis-config.xml mybatis.mapper-locations=mybatis/mappers/**/*Mapper.xml mybatis.executor-type=simple ### Logging logging.level.root=info logging.level.kr.kesti.mybatis.examples=debug 2016-10-12KESTI 개발팀 세미나 12
Database Setup by Flyway /** * Flyway 를 이용한 DB Migration 을 수행하도록 합니다. * * @param dataSource DataSource * @return {@link Flyway} 인스턴스 */ @Bean(initMethod = "migrate") protected Flyway flyway(DataSource dataSource) { Flyway flyway = new Flyway(); flyway.setDataSource(dataSource); if (cleanDatabaseForTest()) { flyway.clean(); } return flyway; } CREATE TABLE Actors ( id SERIAL PRIMARY KEY, firstname VARCHAR(64), lastname VARCHAR(64) ); INSERT INTO Actors (firstname, lastname) VALUES ('Sunghyouk', 'Bae'); INSERT INTO Actors (firstname, lastname) VALUES ('Misook', 'Kwon'); INSERT INTO Actors (firstname, lastname) VALUES ('Jehyoung', 'Bae'); INSERT INTO Actors (firstname, lastname) VALUES ('Jinseok', 'Kwon'); INSERT INTO Actors (firstname, lastname) VALUES ('Kildong', 'Hong'); 2016-10-12KESTI 개발팀 세미나 13
Configuration Test @Slf4j @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = { MyBatisConfiguration.class }) public class MyBatisConfigurationTest { @Inject ActorMapper actorMapper; @Inject ActorRepository actorRepository; @Inject private SqlSessionFactory sf; @Test public void testConfiguration() { assertThat(actorMapper).isNotNull(); assertThat(actorRepository).isNotNull(); assertThat(sf).isNotNull(); } } 2016-10-12KESTI 개발팀 세미나 14
Repository Test @Slf4j @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = { MyBatisConfiguration.class }) public class ActorRepositoryTest { @Inject ActorRepository actorRepo; private static final String FIRST_NAME = "Sunghyouk"; @Test public void testConfiguration() { assertThat(actorRepo).isNotNull(); } @Test public void testFindByFirstname() { Actor actor = actorRepo.findByFirstname(FIRST_NAME); assertThat(actor).isNotNull(); } @Test public void testFindByFirstnameWithXmlMapper() { Actor actor = actorRepo.findByFirstnameWithXmlMapper(FIRST_NAME); assertThat(actor).isNotNull(); } @Test public void testFindAll() { List<Actor> actors = actorRepo.findAll(); assertThat(actors.size()).isGreaterThan(0); } @Test public void testInsertActor() { String firstname = "mybatis"; Actor actor = Actor.of(null, firstname, "kesti"); int rowCount = actorRepo.insertActor(actor); log.debug("rowCount={}", rowCount); actor = actorRepo.findByFirstname(firstname); log.debug("actor={}", actor); assertThat(actor).isNotNull(); assertThat(actor.getFirstname()).isEqualTo(firstname); actorRepo.deleteById(actor.getId()); } } 2016-10-12KESTI 개발팀 세미나 15
Flyway Evolve your Database Schema easily and reliably across all your instances 2016-10-12KESTI 개발팀 세미나 16
Code 관리는 git 2016-10-12KESTI 개발팀 세미나 17 DB 형상 관리는?
DB Schema Control by Dev Step 2016-10-12KESTI 개발팀 세미나 18
DB Schema Version Control 2016-10-12KESTI 개발팀 세미나 19
QueryDSL Unified Queries for Java with Typesafe 2016-10-12KESTI 개발팀 세미나 20
QueryDSL 개요 • 질의어를 문자열이 아닌 Java Code로 표현 • Query문 – type check 불가 / 실행 전에는 오류 검출 불가 • Java Code는 • Compile Error를 미리 검출 • Code assistant 활용 100% • Refactoring 용이 • 다양한 저장소에 대한 일관된 질의어 제작 가능 • Collection, RDBMS, MongoDB, Lucene … • .NET 의 LINQ 같은 목적 (Langunage-INtegrated Query) • 참고 : 한글 매뉴얼 (단 3.4.0 기준임. 4.x 는 package 명이 달라졌음) 2016-10-12KESTI 개발팀 세미나 21
QueryDSL 적용 범위 JPA (Java Persistence API) JDO (Java Data Object) SQL Lucence MongoDB Collections QueryDSL RDBMS ORM Lucene (search engine) MongoD B List / Map 2016-10-12KESTI 개발팀 세미나 22
QueryDSL for SQL • Select • Join (innerJoin, join, leftJoin, rightJoin, fullJoin) • group by / having • order by • limit / offset / restrict • subquery • Window functions • Common Table Expression (CTE) • Insert • Update • Delete 2016-10-12KESTI 개발팀 세미나 23
SELECT with Projections 2016-10-12KESTI 개발팀 세미나 24 QActors $ = QActors.actors; List<Tuple> rows = query.select($.id, $.firstname, $.lastname) .from($) .fetch(); QActors $ = QActors.actors; List<Actor> actors = query.select(Projections.constructor(Actor.class, $.id, $.firstname, $.lastname)) .from($) .fetch();
SELECT – Filter, GroupBy 2016-10-12KESTI 개발팀 세미나 25 Actor actor = query.select(Projections.constructor(Actor.class, $.id, $.firstname, $.lastname)) .from($) .where($.lastname.eq("Bae")) .fetchFirst(); List<Tuple> rows = query.select($.lastname, $.lastname.count().as("cnt")) .from($) .groupBy($.lastname) .fetch();
SELECT : Subquery 2016-10-12KESTI 개발팀 세미나 26 QActors $ = QActors.actors; QActors $2 = new QActors("$2"); SQLQuery<Actor> sq = query.select(Projections.constructor(Actor.class, $.id, $.firstname, $.lastname)) .from($) .where($.id.eq(SQLExpressions.select($2.id.max()).from($2))); List<Actor> actors = sq.fetch();
CUD 2016-10-12KESTI 개발팀 세미나 27 QActors $ = QActors.actors; long inserted = query.insert($) .columns($.firstname, $.lastname) .values("querydsl", "examples") .execute(); long updated = query.update($) .set($.lastname, "updated examples") .where($.firstname.eq("querydsl")) .execute(); long deleted = query.delete($) .where($.firstname.eq("querydsl")) .execute();
CUD Batch Execution 2016-10-12KESTI 개발팀 세미나 28 QActors $ = QActors.actors; int COUNT = 100; // INSERT SQLInsertClause insertClause = query.insert($); for (int i = 0; i < COUNT; i++) { insertClause.set($.firstname, "firstname-" + i) .set($.lastname, "lastname-" + i) .addBatch(); } long insertedCount = insertClause.execute(); // UPDATE SQLUpdateClause updateClause = query.update($); for (int i = 0; i < COUNT; i++) { updateClause.set($.firstname, "updated-firstname-" + i) .where($.lastname.eq("lastname-" + i)) .addBatch(); } long updatedCount = updateClause.execute(); // DELETE SQLDeleteClause deleteClause = query.delete($); for (int i = 0; i < COUNT; i++) { deleteClause.where($.firstname.eq("updated-firstname-" + i)) .addBatch(); } long deletedCount = deleteClause.execute();
QueryDSL Dependency 2016-10-12KESTI 개발팀 세미나 29 <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-sql</artifactId> <version>${com.querydsl.version}</version> </dependency> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-sql-spring</artifactId> <version>${com.querydsl.version}</version> </dependency> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-sql-codegen</artifactId> <version>${com.querydsl.version}</version> </dependency> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-spatial</artifactId> <version>${com.querydsl.version}</version> </dependency> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-sql-spatial</artifactId> <version>${com.querydsl.version}</version> </dependency>
QueryDSL APT 2016-10-12KESTI 개발팀 세미나 30 <plugin> <groupId>com.querydsl</groupId> <artifactId>querydsl-maven-plugin</artifactId> <version>${com.querydsl.version}</version> <executions> <execution> <goals> <goal>export</goal> </goals> </execution> </executions> <configuration> <jdbcDriver>org.postgresql.Driver</jdbcDriver> <jdbcUrl>jdbc:postgresql://localhost/querydsl</jdbcUrl> <jdbcUser>root</jdbcUser> <jdbcPassword>root</jdbcPassword> <packageName>kesti4j.data.querydsl.models</packageName> <sourceFolder>${project.basedir}/target/generated-sources/java</sourceFolder> <targetFolder>${project.basedir}/target/generated-sources/java</targetFolder> <spatial>true</spatial> </configuration> <dependencies> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.4-1206-jdbc42</version> </dependency> </dependencies> </plugin>
QueryDSL Generated Code 2016-10-12KESTI 개발팀 세미나 31 /** * QActors is a Querydsl query type for QActors */ @Generated("com.querydsl.sql.codegen.MetaDataSerializer") public class QActors extends RelationalPathSpatial<QActors> { private static final long serialVersionUID = 822224394; public static final QActors actors = new QActors("actors"); public final StringPath firstname = createString("firstname"); public final NumberPath<Integer> id = createNumber("id", Integer.class); public final StringPath lastname = createString("lastname"); public final com.querydsl.sql.PrimaryKey<QActors> actorsPkey = createPrimaryKey(id); public QActors(String variable) { super(QActors.class, forVariable(variable), "public", "actors"); addMetadata(); } public QActors(String variable, String schema, String table) { super(QActors.class, forVariable(variable), schema, table); addMetadata(); } public QActors(Path<? extends QActors> path) { super(path.getType(), path.getMetadata(), "public", "actors"); addMetadata(); } public QActors(PathMetadata metadata) { super(QActors.class, metadata, "public", "actors"); addMetadata(); } public void addMetadata() { addMetadata(firstname, ColumnMetadata.named("firstname").withIndex(2).ofType(Types.VARCHAR).withSize(64)); addMetadata(id, ColumnMetadata.named("id").withIndex(1).ofType(Types.INTEGER).withSize(10).notNull()); addMetadata(lastname, ColumnMetadata.named("lastname").withIndex(3).ofType(Types.VARCHAR).withSize(64)); } }

SpringBoot with MyBatis, Flyway, QueryDSL

  • 1.
    Spring Boot withMyBatis KESTI 개발팀
  • 2.
    Spring Boot withMyBatis • mybatis-spring-boot-starter 소개 • MyBatis Tutorials • setup • mappers • testing • Bonus Tutorials • Flyway • QueryDSL 2016-10-12KESTI 개발팀 세미나 2
  • 3.
    MyBatis for SpringBoot • MyBatis 를 Spring Boot Application 에서 사용하기 위한 라이브러리 • 홈페이지 : http://www.mybatis.org/mybatis-spring-boot/ • 소스 : https://github.com/mybatis/mybatis-spring-boot • 예제 : • KESTI SpringBoot-MyBatis Tutorials • Spring Boot 에서 Java Config를 통해 myBatis 연동하기 2016-10-12KESTI 개발팀 세미나 3
  • 4.
  • 5.
    Tutorial Overview 1. mybatis-spring-boot-starter프로젝트 구성 2. Mapper – Annotation / XML 방식 3. MyBatis 설정 방법 4. Flyway 를 이용한 database migration 방법 5. MyBatis 단위 테스트 2016-10-12KESTI 개발팀 세미나 5
  • 6.
  • 7.
    Project Structures configuration data handlingsource Spring Boot Application Flyway database migration MyBatis configuration & XML Mappers 환경설정 정보 Test 코드 2016-10-12KESTI 개발팀 세미나 7
  • 8.
    XML Mapper <?xml version="1.0"encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="kr.kesti.mybatis.examples.domain.models"> <select id="selectActorByFirstname" parameterType="String" resultType="Actor"> SELECT * FROM Actors WHERE firstname = #{firstname} limit 1 </select> <insert id="insertActor" useGeneratedKeys="true" keyProperty="id"> insert into Actors(firstname, lastname) values( #{firstname}, #{lastname} ) </insert> <!-- Oracle, PostgreSQL 은 SEQUENCE --> <!-- <insert id="insertActorBySequence"> <selectKey keyProperty="id" resultType="int" order="BEFORE" statementType="PREPARED"> SELECT SEQ_ID.nextval FROM DUAL </selectKey> INSERT INTO Autors (id, firstname, lastname) VALUES (#{id}, #{firstname}, #{lastname}) </insert> --> </mapper> 2016-10-12KESTI 개발팀 세미나 8
  • 9.
    Annotated Mapper /** * Actor를 위한 MyBatis Mapper 입니다. */ public interface ActorMapper { @Select("SELECT * FROM Actors WHERE firstname = #{firstname}") Actor findByFirstname(@Param("firstname") String firstname); @Select("SELECT * FROM Actors") List<Actor> findAll(); @Delete("DELETE FROM Actors WHERE id=#{id}") void deleteById(@Param("id") Integer id); } /** * MyBatis 를 Spring boot 에서 사용하기 위한 환경설정 */ @Configuration @EnableAutoConfiguration @ComponentScan(basePackageClasses = { ActorRepository.class }) @MapperScan(basePackageClasses = { ActorMapper.class }) public class MyBatisConfiguration extends AbstractFlywayMyBatisConfiguration { 2016-10-12KESTI 개발팀 세미나 9 XML Mapper 보다 좋은점은?
  • 10.
    Repository / DAO /** *{@link Actor} 를 위한 Repository (DAO) 입니다. */ @Slf4j @Repository public class ActorRepository { @Autowired ActorMapper mapper; @Autowired SqlSessionTemplate session; public Actor findByFirstname(@NonNull String firstname) { return mapper.findByFirstname(firstname); } public Actor findByFirstnameWithXmlMapper(@NonNull String firstname) { return session.selectOne("selectActorByFirstname", firstname); } public List<Actor> findAll() { return mapper.findAll(); } public int insertActor(@NonNull Actor actor) { return session.insert("insertActor", actor); } public void deleteById(@NonNull Integer id) { mapper.deleteById(id); } } 2016-10-12KESTI 개발팀 세미나 10
  • 11.
    MyBatis JavaConfig @Configuration @EnableAutoConfiguration @ComponentScan(basePackageClasses ={ ActorRepository.class }) @MapperScan(basePackageClasses = { ActorMapper.class }) public class MyBatisConfiguration extends AbstractFlywayMyBatisConfiguration { // datasource 용 properties (application.properties 에서 자동으로 정보를 읽어옴) @Inject DataSourceProperties dataSourceProperties; // mybatis 용 properties (application.properties 에서 자동으로 정보를 읽어옴) @Inject MybatisProperties mybatisProperties; @Override protected DatabaseSetting getDatabaseSetting() { return DatabaseSetting.builder() .driverClass(dataSourceProperties.getDriverClassName()) .jdbcUrl(dataSourceProperties.getUrl()) .build(); } @Override protected String getMyBatisConfigPath() { return mybatisProperties.getConfig(); } @Override protected String getMyBatisMapperPath() { return mybatisProperties.getMapperLocations()[0]; } } 2016-10-12KESTI 개발팀 세미나 11 XML Config 와 장단점 비교
  • 12.
    Spring Boot application.properties ###DataSource spring.datasource.driver-class-name=org.h2.Driver spring.datasource.jdbc-url=jdbc:h2:mem spring.datasource.username=sa spring.datasource.password= ### MyBatis mybatis.config=mybatis/mybatis-config.xml mybatis.mapper-locations=mybatis/mappers/**/*Mapper.xml mybatis.executor-type=simple ### Logging logging.level.root=info logging.level.kr.kesti.mybatis.examples=debug 2016-10-12KESTI 개발팀 세미나 12
  • 13.
    Database Setup byFlyway /** * Flyway 를 이용한 DB Migration 을 수행하도록 합니다. * * @param dataSource DataSource * @return {@link Flyway} 인스턴스 */ @Bean(initMethod = "migrate") protected Flyway flyway(DataSource dataSource) { Flyway flyway = new Flyway(); flyway.setDataSource(dataSource); if (cleanDatabaseForTest()) { flyway.clean(); } return flyway; } CREATE TABLE Actors ( id SERIAL PRIMARY KEY, firstname VARCHAR(64), lastname VARCHAR(64) ); INSERT INTO Actors (firstname, lastname) VALUES ('Sunghyouk', 'Bae'); INSERT INTO Actors (firstname, lastname) VALUES ('Misook', 'Kwon'); INSERT INTO Actors (firstname, lastname) VALUES ('Jehyoung', 'Bae'); INSERT INTO Actors (firstname, lastname) VALUES ('Jinseok', 'Kwon'); INSERT INTO Actors (firstname, lastname) VALUES ('Kildong', 'Hong'); 2016-10-12KESTI 개발팀 세미나 13
  • 14.
    Configuration Test @Slf4j @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes ={ MyBatisConfiguration.class }) public class MyBatisConfigurationTest { @Inject ActorMapper actorMapper; @Inject ActorRepository actorRepository; @Inject private SqlSessionFactory sf; @Test public void testConfiguration() { assertThat(actorMapper).isNotNull(); assertThat(actorRepository).isNotNull(); assertThat(sf).isNotNull(); } } 2016-10-12KESTI 개발팀 세미나 14
  • 15.
    Repository Test @Slf4j @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes ={ MyBatisConfiguration.class }) public class ActorRepositoryTest { @Inject ActorRepository actorRepo; private static final String FIRST_NAME = "Sunghyouk"; @Test public void testConfiguration() { assertThat(actorRepo).isNotNull(); } @Test public void testFindByFirstname() { Actor actor = actorRepo.findByFirstname(FIRST_NAME); assertThat(actor).isNotNull(); } @Test public void testFindByFirstnameWithXmlMapper() { Actor actor = actorRepo.findByFirstnameWithXmlMapper(FIRST_NAME); assertThat(actor).isNotNull(); } @Test public void testFindAll() { List<Actor> actors = actorRepo.findAll(); assertThat(actors.size()).isGreaterThan(0); } @Test public void testInsertActor() { String firstname = "mybatis"; Actor actor = Actor.of(null, firstname, "kesti"); int rowCount = actorRepo.insertActor(actor); log.debug("rowCount={}", rowCount); actor = actorRepo.findByFirstname(firstname); log.debug("actor={}", actor); assertThat(actor).isNotNull(); assertThat(actor.getFirstname()).isEqualTo(firstname); actorRepo.deleteById(actor.getId()); } } 2016-10-12KESTI 개발팀 세미나 15
  • 16.
    Flyway Evolve your DatabaseSchema easily and reliably across all your instances 2016-10-12KESTI 개발팀 세미나 16
  • 17.
    Code 관리는 git 2016-10-12KESTI개발팀 세미나 17 DB 형상 관리는?
  • 18.
    DB Schema Controlby Dev Step 2016-10-12KESTI 개발팀 세미나 18
  • 19.
    DB Schema VersionControl 2016-10-12KESTI 개발팀 세미나 19
  • 20.
    QueryDSL Unified Queries forJava with Typesafe 2016-10-12KESTI 개발팀 세미나 20
  • 21.
    QueryDSL 개요 • 질의어를문자열이 아닌 Java Code로 표현 • Query문 – type check 불가 / 실행 전에는 오류 검출 불가 • Java Code는 • Compile Error를 미리 검출 • Code assistant 활용 100% • Refactoring 용이 • 다양한 저장소에 대한 일관된 질의어 제작 가능 • Collection, RDBMS, MongoDB, Lucene … • .NET 의 LINQ 같은 목적 (Langunage-INtegrated Query) • 참고 : 한글 매뉴얼 (단 3.4.0 기준임. 4.x 는 package 명이 달라졌음) 2016-10-12KESTI 개발팀 세미나 21
  • 22.
    QueryDSL 적용 범위 JPA(Java Persistence API) JDO (Java Data Object) SQL Lucence MongoDB Collections QueryDSL RDBMS ORM Lucene (search engine) MongoD B List / Map 2016-10-12KESTI 개발팀 세미나 22
  • 23.
    QueryDSL for SQL •Select • Join (innerJoin, join, leftJoin, rightJoin, fullJoin) • group by / having • order by • limit / offset / restrict • subquery • Window functions • Common Table Expression (CTE) • Insert • Update • Delete 2016-10-12KESTI 개발팀 세미나 23
  • 24.
    SELECT with Projections 2016-10-12KESTI개발팀 세미나 24 QActors $ = QActors.actors; List<Tuple> rows = query.select($.id, $.firstname, $.lastname) .from($) .fetch(); QActors $ = QActors.actors; List<Actor> actors = query.select(Projections.constructor(Actor.class, $.id, $.firstname, $.lastname)) .from($) .fetch();
  • 25.
    SELECT – Filter,GroupBy 2016-10-12KESTI 개발팀 세미나 25 Actor actor = query.select(Projections.constructor(Actor.class, $.id, $.firstname, $.lastname)) .from($) .where($.lastname.eq("Bae")) .fetchFirst(); List<Tuple> rows = query.select($.lastname, $.lastname.count().as("cnt")) .from($) .groupBy($.lastname) .fetch();
  • 26.
    SELECT : Subquery 2016-10-12KESTI개발팀 세미나 26 QActors $ = QActors.actors; QActors $2 = new QActors("$2"); SQLQuery<Actor> sq = query.select(Projections.constructor(Actor.class, $.id, $.firstname, $.lastname)) .from($) .where($.id.eq(SQLExpressions.select($2.id.max()).from($2))); List<Actor> actors = sq.fetch();
  • 27.
    CUD 2016-10-12KESTI 개발팀 세미나27 QActors $ = QActors.actors; long inserted = query.insert($) .columns($.firstname, $.lastname) .values("querydsl", "examples") .execute(); long updated = query.update($) .set($.lastname, "updated examples") .where($.firstname.eq("querydsl")) .execute(); long deleted = query.delete($) .where($.firstname.eq("querydsl")) .execute();
  • 28.
    CUD Batch Execution 2016-10-12KESTI개발팀 세미나 28 QActors $ = QActors.actors; int COUNT = 100; // INSERT SQLInsertClause insertClause = query.insert($); for (int i = 0; i < COUNT; i++) { insertClause.set($.firstname, "firstname-" + i) .set($.lastname, "lastname-" + i) .addBatch(); } long insertedCount = insertClause.execute(); // UPDATE SQLUpdateClause updateClause = query.update($); for (int i = 0; i < COUNT; i++) { updateClause.set($.firstname, "updated-firstname-" + i) .where($.lastname.eq("lastname-" + i)) .addBatch(); } long updatedCount = updateClause.execute(); // DELETE SQLDeleteClause deleteClause = query.delete($); for (int i = 0; i < COUNT; i++) { deleteClause.where($.firstname.eq("updated-firstname-" + i)) .addBatch(); } long deletedCount = deleteClause.execute();
  • 29.
    QueryDSL Dependency 2016-10-12KESTI 개발팀세미나 29 <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-sql</artifactId> <version>${com.querydsl.version}</version> </dependency> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-sql-spring</artifactId> <version>${com.querydsl.version}</version> </dependency> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-sql-codegen</artifactId> <version>${com.querydsl.version}</version> </dependency> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-spatial</artifactId> <version>${com.querydsl.version}</version> </dependency> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-sql-spatial</artifactId> <version>${com.querydsl.version}</version> </dependency>
  • 30.
    QueryDSL APT 2016-10-12KESTI 개발팀세미나 30 <plugin> <groupId>com.querydsl</groupId> <artifactId>querydsl-maven-plugin</artifactId> <version>${com.querydsl.version}</version> <executions> <execution> <goals> <goal>export</goal> </goals> </execution> </executions> <configuration> <jdbcDriver>org.postgresql.Driver</jdbcDriver> <jdbcUrl>jdbc:postgresql://localhost/querydsl</jdbcUrl> <jdbcUser>root</jdbcUser> <jdbcPassword>root</jdbcPassword> <packageName>kesti4j.data.querydsl.models</packageName> <sourceFolder>${project.basedir}/target/generated-sources/java</sourceFolder> <targetFolder>${project.basedir}/target/generated-sources/java</targetFolder> <spatial>true</spatial> </configuration> <dependencies> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.4-1206-jdbc42</version> </dependency> </dependencies> </plugin>
  • 31.
    QueryDSL Generated Code 2016-10-12KESTI개발팀 세미나 31 /** * QActors is a Querydsl query type for QActors */ @Generated("com.querydsl.sql.codegen.MetaDataSerializer") public class QActors extends RelationalPathSpatial<QActors> { private static final long serialVersionUID = 822224394; public static final QActors actors = new QActors("actors"); public final StringPath firstname = createString("firstname"); public final NumberPath<Integer> id = createNumber("id", Integer.class); public final StringPath lastname = createString("lastname"); public final com.querydsl.sql.PrimaryKey<QActors> actorsPkey = createPrimaryKey(id); public QActors(String variable) { super(QActors.class, forVariable(variable), "public", "actors"); addMetadata(); } public QActors(String variable, String schema, String table) { super(QActors.class, forVariable(variable), schema, table); addMetadata(); } public QActors(Path<? extends QActors> path) { super(path.getType(), path.getMetadata(), "public", "actors"); addMetadata(); } public QActors(PathMetadata metadata) { super(QActors.class, metadata, "public", "actors"); addMetadata(); } public void addMetadata() { addMetadata(firstname, ColumnMetadata.named("firstname").withIndex(2).ofType(Types.VARCHAR).withSize(64)); addMetadata(id, ColumnMetadata.named("id").withIndex(1).ofType(Types.INTEGER).withSize(10).notNull()); addMetadata(lastname, ColumnMetadata.named("lastname").withIndex(3).ofType(Types.VARCHAR).withSize(64)); } }