31
Spring Boot with MyBatis KESTI 개개개

SpringBoot with MyBatis, Flyway, QueryDSL

Embed Size (px)

Citation preview

Page 1: SpringBoot with MyBatis, Flyway, QueryDSL

Spring Boot with MyBatisKESTI 개발팀

Page 2: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 2

Spring Boot with MyBatis • mybatis-spring-boot-starter 소개• MyBatis Tutorials

• setup• mappers• testing

• Bonus Tutorials• Flyway• QueryDSL

KESTI 개발팀 세미나

Page 3: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 3

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 연동하기

KESTI 개발팀 세미나

Page 4: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 4

MyBatis Tutorials

KESTI 개발팀 세미나

Page 5: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 5

Tutorial Overview1. mybatis-spring-boot-starter 프로젝트 구성2. Mapper – Annotation / XML 방식3. MyBatis 설정 방법4. Flyway 를 이용한 database migration 방법5. MyBatis 단위 테스트

KESTI 개발팀 세미나

Page 6: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 6

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>

KESTI 개발팀 세미나

Page 7: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 7

Project Structuresconfiguration

data handling source

Spring Boot Application

Flyway database migration

MyBatis configuration & XML Mappers

환경설정 정보

Test 코드

KESTI 개발팀 세미나

Page 8: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 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>

KESTI 개발팀 세미나

Page 9: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 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 {

KESTI 개발팀 세미나

XML Mapper 보다 좋은점은 ?

Page 10: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 10

Repository / DAO/** * {@link Actor} 를 위한 Repository (DAO) 입니다 . */@Slf4j@Repositorypublic 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); }}KESTI 개발팀 세미나

Page 11: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 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]; }}

KESTI 개발팀 세미나

XML Config 와 장단점 비교

Page 12: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 12

Spring Boot application.properties

### DataSourcespring.datasource.driver-class-name=org.h2.Driverspring.datasource.jdbc-url=jdbc:h2:memspring.datasource.username=saspring.datasource.password=

### MyBatismybatis.config=mybatis/mybatis-config.xmlmybatis.mapper-locations=mybatis/mappers/**/*Mapper.xmlmybatis.executor-type=simple

### Logginglogging.level.root=infologging.level.kr.kesti.mybatis.examples=debug

KESTI 개발팀 세미나

Page 13: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 13

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');

KESTI 개발팀 세미나

Page 14: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 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(); }}

KESTI 개발팀 세미나

Page 15: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 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()); }}KESTI 개발팀 세미나

Page 16: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 16

FlywayEvolve your Database Schema easily and

reliably across all your instances

KESTI 개발팀 세미나

Page 17: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 17

Code 관리는 git

KESTI 개발팀 세미나

DB 형상 관리는 ?

Page 18: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 18

DB Schema Control by Dev Step

KESTI 개발팀 세미나

Page 19: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 19

DB Schema Version Control

KESTI 개발팀 세미나

Page 20: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 20

QueryDSLUnified Queries for Java with Typesafe

KESTI 개발팀 세미나

Page 21: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 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 명이 달라졌음 )

KESTI 개발팀 세미나

Page 22: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 22

QueryDSL 적용 범위JPA (Java Persistence API)

JDO (Java Data Object)

SQL

Lucence

MongoDB

Collections

QueryDSL

RDBMS

ORM

Lucene(search engine)

MongoDB

List / Map

KESTI 개발팀 세미나

Page 23: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 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• DeleteKESTI 개발팀 세미나

Page 24: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 24

SELECT with Projections

KESTI 개발팀 세미나

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

Page 25: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 25

SELECT – Filter, GroupBy

KESTI 개발팀 세미나

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

Page 26: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 26

SELECT : Subquery

KESTI 개발팀 세미나

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

Page 27: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 27

CUD

KESTI 개발팀 세미나

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

Page 28: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 28

CUD Batch Execution

KESTI 개발팀 세미나

QActors $ = QActors.actors;int COUNT = 100;

// INSERTSQLInsertClause insertClause = query.insert($);for (int i = 0; i < COUNT; i++) { insertClause.set($.firstname, "firstname-" + i) .set($.lastname, "lastname-" + i) .addBatch();}long insertedCount = insertClause.execute();

// UPDATESQLUpdateClause 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();

// DELETESQLDeleteClause deleteClause = query.delete($);for (int i = 0; i < COUNT; i++) { deleteClause.where($.firstname.eq("updated-firstname-" + i)) .addBatch();}long deletedCount = deleteClause.execute();

Page 29: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 29

QueryDSL Dependency

KESTI 개발팀 세미나

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

Page 30: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 30

QueryDSL APT

KESTI 개발팀 세미나

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

Page 31: SpringBoot with MyBatis, Flyway, QueryDSL

05/02/2023 31

QueryDSL Generated Code

KESTI 개발팀 세미나

/** * 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)); }}