This commit is contained in:
mindol1004
2024-08-23 16:46:23 +09:00
parent f04d6df324
commit ade2aa0733
22 changed files with 240 additions and 263 deletions

View File

@@ -1,7 +1,7 @@
package com.spring.domain.post.entity;
import com.spring.infra.db.SecondaryDataSourceConfig;
import com.spring.infra.db.annotation.DatabaseSelector;
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
@@ -10,24 +10,22 @@ import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@DatabaseSelector(SecondaryDataSourceConfig.DATABASE)
@Entity
@Table(name = "APP_POST")
@Getter
@RequiredArgsConstructor
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "POST_ID", nullable = false)
private final Long postId;
private Long postId;
@Column(name = "TITLE", nullable = false, length = 100)
private final String title;
private String title;
@Column(name = "CONTENT", nullable = false, length = 2000)
private final String content;
private String content;
}

View File

@@ -0,0 +1,8 @@
package com.spring.domain.post.mapper;
import com.spring.infra.db.orm.mybatis.annotation.SecondaryMapper;
@SecondaryMapper
public interface PostMapper {
}

View File

@@ -4,7 +4,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import com.spring.domain.post.entity.Post;
import com.spring.infra.db.SecondaryDataSourceConfig;
import com.spring.infra.db.annotation.DatabaseSelector;
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
@DatabaseSelector(SecondaryDataSourceConfig.DATABASE)
public interface PostRepository extends JpaRepository<Post, Long> {

View File

@@ -7,7 +7,6 @@ import jakarta.persistence.Embeddable;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinColumns;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.EqualsAndHashCode;
@@ -25,11 +24,9 @@ public class QrtzBlobTriggers {
private byte[] blobData;
@ManyToOne
@JoinColumns({
@JoinColumn(name = "SCHED_NAME", referencedColumnName = "SCHED_NAME", insertable = false, updatable = false),
@JoinColumn(name = "TRIGGER_NAME", referencedColumnName = "TRIGGER_NAME", insertable = false, updatable = false),
@JoinColumn(name = "TRIGGER_GROUP", referencedColumnName = "TRIGGER_GROUP", insertable = false, updatable = false)
})
@JoinColumn(name = "SCHED_NAME", referencedColumnName = "SCHED_NAME", insertable = false, updatable = false)
@JoinColumn(name = "TRIGGER_NAME", referencedColumnName = "TRIGGER_NAME", insertable = false, updatable = false)
@JoinColumn(name = "TRIGGER_GROUP", referencedColumnName = "TRIGGER_GROUP", insertable = false, updatable = false)
private QrtzTriggers qrtzTriggers;
@Embeddable

View File

@@ -12,29 +12,27 @@ import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Entity
@Table(name = "APP_USER")
@Getter
@RequiredArgsConstructor
public final class AppUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "USER_ID", nullable = false)
private final Long userId;
private Long userId;
@Column(name = "LOGIN_ID", nullable = false, length = 50)
private final String loginId;
private String loginId;
@Column(name = "PASSWORD", nullable = false, length = 128)
private final String password;
private String password;
@Column(name = "USER_NAME", nullable = false, length = 50)
private final String userName;
private String userName;
@OneToMany(mappedBy = "appUser", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private final Set<AppUserRoleMap> appUserRoleMap;
private Set<AppUserRoleMap> appUserRoleMap;
}

View File

@@ -12,23 +12,21 @@ import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Entity
@Table(name = "APP_USER_ROLE")
@Getter
@RequiredArgsConstructor
public final class AppUserRole {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ROLE_ID", nullable = false)
private final Long roleId;
private Long roleId;
@Column(name = "ROLE_TYPE", nullable = false, length = 10)
private final String roleType;
private String roleType;
@OneToMany(mappedBy = "appUserRole", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private final Set<AppUserRoleMap> appUserRoleMap;
private Set<AppUserRoleMap> appUserRoleMap;
}

View File

@@ -12,37 +12,34 @@ import jakarta.persistence.MapsId;
import jakarta.persistence.Table;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Entity
@Table(name = "APP_USER_ROLE_MAP")
@Getter
@RequiredArgsConstructor
public final class AppUserRoleMap {
@EmbeddedId
private final AppUserRoleMapId id;
private AppUserRoleMapId id;
@ManyToOne
@MapsId("roleId")
@JoinColumn(name = "ROLE_ID")
private final AppUserRole appUserRole;
private AppUserRole appUserRole;
@ManyToOne
@MapsId("userId")
@JoinColumn(name = "USER_ID")
private final AppUser appUser;
private AppUser appUser;
@Embeddable
@EqualsAndHashCode
@Getter
@RequiredArgsConstructor
public static final class AppUserRoleMapId implements Serializable {
public static class AppUserRoleMapId implements Serializable {
@Column(name = "ROLE_ID", nullable = false)
private final Long roleId;
private Long roleId;
@Column(name = "USER_ID", nullable = false)
private final Long userId;
private Long userId;
}
}

View File

@@ -0,0 +1,11 @@
package com.spring.domain.user.mapper;
import java.util.Optional;
import com.spring.domain.user.entity.AppUser;
import com.spring.infra.db.orm.mybatis.annotation.PrimaryMapper;
@PrimaryMapper
public interface AppUserMapper {
Optional<AppUser> findByLoginId(String loginId);
}

View File

@@ -3,46 +3,48 @@ package com.spring.infra.db.orm.jpa;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Primary;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.lang.NonNull;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import com.spring.infra.db.PrimaryDataSourceConfig;
import com.spring.infra.db.orm.jpa.util.DatabaseSelectorFilter;
import com.spring.infra.db.orm.jpa.util.EntityScannerConfigurer;
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
import com.spring.infra.db.orm.jpa.util.EntityScanner;
import jakarta.persistence.EntityManagerFactory;
import lombok.RequiredArgsConstructor;
@Configuration
@EnableJpaRepositories(
basePackages = {PrimaryJpaConfig.BASE_PACKAGE},
basePackages = PrimaryJpaConfig.BASE_PACKAGE,
entityManagerFactoryRef = PrimaryJpaConfig.ENTITY_MANAGER_FACTORY,
transactionManagerRef = PrimaryJpaConfig.TRANSACTION_MANAGER,
includeFilters = @Filter(type = FilterType.CUSTOM, classes = DatabaseSelectorFilter.class)
includeFilters = @Filter(type = FilterType.CUSTOM, classes = PrimaryJpaConfig.DatabaseFilter.class)
)
@RequiredArgsConstructor
public class PrimaryJpaConfig {
public static final String TRANSACTION_MANAGER = "primaryTransactionManager";
private static final String DATABASE_FILTER = "primaryDatabaseFilter";
private static final String BASE_PACKAGE = "com.spring.domain";
private static final String ENTITY_MANAGER_FACTORY = "primaryEntityManagerFactory";
private static final String PERSISTENCE_UNIT = "primaryPersistenceUnit";
@Primary
@Bean(name = DATABASE_FILTER)
DatabaseSelectorFilter databaseSelectorFilter() {
var filter = new DatabaseSelectorFilter();
filter.setDbName(PrimaryDataSourceConfig.DATABASE);
return filter;
}
private final JpaProperties jpaProperties;
private final HibernateProperties hibernateProperties;
@Primary
@Bean(name = ENTITY_MANAGER_FACTORY)
@@ -50,20 +52,27 @@ public class PrimaryJpaConfig {
EntityManagerFactoryBuilder builder,
@Qualifier(PrimaryDataSourceConfig.DATASOURCE) DataSource dataSource
) {
var entities = EntityScannerConfigurer.scanEntities(BASE_PACKAGE, PrimaryDataSourceConfig.DATABASE);
var properties = hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings());
var entities = EntityScanner.scanEntities(BASE_PACKAGE);
return builder
.dataSource(dataSource)
.packages(entities.stream().map(BeanDefinition::getBeanClassName).toArray(String[]::new))
.packages(entities)
.persistenceUnit(PERSISTENCE_UNIT)
.properties(properties)
.build();
}
@Primary
@Bean(TRANSACTION_MANAGER)
PlatformTransactionManager transactionManager(
@Qualifier(ENTITY_MANAGER_FACTORY) EntityManagerFactory entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
PlatformTransactionManager transactionManager(@Qualifier(ENTITY_MANAGER_FACTORY) EntityManagerFactory factory) {
return new JpaTransactionManager(factory);
}
public static class DatabaseFilter implements TypeFilter {
@Override
public boolean match(@NonNull MetadataReader reader, @NonNull MetadataReaderFactory factory) {
return !reader.getAnnotationMetadata().hasAnnotation(DatabaseSelector.class.getName());
}
}
}

View File

@@ -1,65 +1,79 @@
package com.spring.infra.db.orm.jpa;
import java.util.Optional;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.lang.NonNull;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import com.spring.infra.db.SecondaryDataSourceConfig;
import com.spring.infra.db.orm.jpa.util.DatabaseSelectorFilter;
import com.spring.infra.db.orm.jpa.util.EntityScannerConfigurer;
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
import com.spring.infra.db.orm.jpa.util.EntityScanner;
import jakarta.persistence.EntityManagerFactory;
import lombok.RequiredArgsConstructor;
@Configuration
@EnableJpaRepositories(
basePackages = {SecondaryJpaConfig.BASE_PACKAGE},
basePackages = SecondaryJpaConfig.BASE_PACKAGE,
entityManagerFactoryRef = SecondaryJpaConfig.ENTITY_MANAGER_FACTORY,
transactionManagerRef = SecondaryJpaConfig.TRANSACTION_MANAGER,
includeFilters = @Filter(type = FilterType.CUSTOM, classes = DatabaseSelectorFilter.class)
includeFilters = @Filter(type = FilterType.CUSTOM, classes = SecondaryJpaConfig.DatabaseFilter.class)
)
@RequiredArgsConstructor
public class SecondaryJpaConfig {
public static final String TRANSACTION_MANAGER = "secondaryTransactionManager";
private static final String DATABASE_FILTER = "secondaryDatabaseFilter";
private static final String BASE_PACKAGE = "com.spring.domain";
private static final String ENTITY_MANAGER_FACTORY = "secondaryEntityManagerFactory";
private static final String PERSISTENCE_UNIT = "secondaryPersistenceUnit";
@Bean(name = DATABASE_FILTER)
DatabaseSelectorFilter databaseSelectorFilter() {
var filter = new DatabaseSelectorFilter();
filter.setDbName(SecondaryDataSourceConfig.DATABASE);
return filter;
}
private final JpaProperties jpaProperties;
private final HibernateProperties hibernateProperties;
@Bean(name = ENTITY_MANAGER_FACTORY)
LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
@Qualifier(SecondaryDataSourceConfig.DATASOURCE) DataSource dataSource
) {
var entities = EntityScannerConfigurer.scanEntities(BASE_PACKAGE, SecondaryDataSourceConfig.DATABASE);
var properties = hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings());
var entities = EntityScanner.scanEntities(BASE_PACKAGE, SecondaryDataSourceConfig.DATABASE);
return builder
.dataSource(dataSource)
.packages(entities.stream().map(BeanDefinition::getBeanClassName).toArray(String[]::new))
.packages(entities)
.persistenceUnit(PERSISTENCE_UNIT)
.properties(properties)
.build();
}
@Bean(TRANSACTION_MANAGER)
PlatformTransactionManager transactionManager(
@Qualifier(ENTITY_MANAGER_FACTORY) EntityManagerFactory entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
PlatformTransactionManager transactionManager(@Qualifier(ENTITY_MANAGER_FACTORY) EntityManagerFactory factory) {
return new JpaTransactionManager(factory);
}
public static class DatabaseFilter implements TypeFilter {
@Override
public boolean match(@NonNull MetadataReader reader, @NonNull MetadataReaderFactory factory) {
return Optional.ofNullable(reader.getAnnotationMetadata().getAnnotationAttributes(DatabaseSelector.class.getName()))
.map(attributes -> SecondaryDataSourceConfig.DATABASE.equals(attributes.get("value")))
.orElse(false);
}
}
}

View File

@@ -1,4 +1,4 @@
package com.spring.infra.db.annotation;
package com.spring.infra.db.orm.jpa.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;

View File

@@ -1,33 +0,0 @@
package com.spring.infra.db.orm.jpa.util;
import java.io.IOException;
import java.util.Optional;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.lang.NonNull;
import com.spring.infra.db.annotation.DatabaseSelector;
import lombok.Setter;
public class DatabaseSelectorFilter implements TypeFilter {
@Setter private String dbName;
@Override
public boolean match(@NonNull MetadataReader metadataReader, @NonNull MetadataReaderFactory metadataReaderFactory)
throws IOException {
boolean hasAnnotation = metadataReader.getAnnotationMetadata().hasAnnotation(DatabaseSelector.class.getName());
if (!hasAnnotation) {
return true;
} else {
return Optional.ofNullable(metadataReader.getAnnotationMetadata()
.getAnnotationAttributes(DatabaseSelector.class.getName()))
.map(attributes -> dbName.equals(attributes.get("value")))
.orElse(false);
}
}
}

View File

@@ -0,0 +1,42 @@
package com.spring.infra.db.orm.jpa.util;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.StringUtils;
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
import jakarta.persistence.Entity;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class EntityScanner {
public static String[] scanEntities(String basePackage) {
return scanEntities(basePackage, null);
}
public static String[] scanEntities(String basePackage, String dbName) {
var scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter((MetadataReader reader, MetadataReaderFactory factory) -> {
if (!reader.getAnnotationMetadata().hasAnnotation(Entity.class.getName())) {
return false;
}
if (StringUtils.hasText(dbName)) {
var attributes = reader.getAnnotationMetadata().getAnnotationAttributes(DatabaseSelector.class.getName());
return attributes != null && dbName.equals(attributes.get("value"));
}
return !reader.getAnnotationMetadata().hasAnnotation(DatabaseSelector.class.getName());
});
return scanner.findCandidateComponents(basePackage).stream()
.map(BeanDefinition::getBeanClassName)
.map(className -> className.substring(0, className.lastIndexOf('.')))
.distinct()
.toArray(String[]::new);
}
}

View File

@@ -1,36 +0,0 @@
package com.spring.infra.db.orm.jpa.util;
import java.util.Optional;
import java.util.Set;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.lang.NonNull;
import com.spring.infra.db.annotation.DatabaseSelector;
public class EntityScannerConfigurer {
public static Set<BeanDefinition> scanEntities(String basePackage, String dbName) {
var scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new TypeFilter() {
@Override
public boolean match(@NonNull MetadataReader metadataReader, @NonNull MetadataReaderFactory metadataReaderFactory) {
boolean hasAnnotation = metadataReader.getAnnotationMetadata().hasAnnotation(DatabaseSelector.class.getName());
if (!hasAnnotation) {
return true;
} else {
return Optional.ofNullable(metadataReader.getAnnotationMetadata()
.getAnnotationAttributes(DatabaseSelector.class.getName()))
.map(attributes -> dbName.equals(attributes.get("value")))
.orElse(false);
}
}
});
return scanner.findCandidateComponents(basePackage);
}
}

View File

@@ -14,40 +14,21 @@ import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import com.spring.infra.db.PrimaryDataSourceConfig;
import com.spring.infra.db.orm.mybatis.util.MybatisBeanNameGenerator;
import com.spring.infra.db.orm.mybatis.annotation.PrimaryMapper;
@Configuration
@MapperScan(
basePackages = PrimaryMybatisConfig.BASE_PACKAGE,
basePackages = {PrimaryMybatisConfig.BASE_PACKAGE},
sqlSessionFactoryRef = PrimaryMybatisConfig.SESSION_FACTORY,
nameGenerator = MybatisBeanNameGenerator.class
annotationClass = PrimaryMapper.class
)
public class PrimaryMybatisConfig {
private static final String BEAN_NAME_GENERATOR = "primaryNameGenerator";
private static final String BASE_PACKAGE = "com.spring.domain";
private static final String ALIASES_PACKAGE = "com.spring.domain.*.*.dto";
private static final String BASE_PACKAGE = "com.spring.domain.*.mapper";
private static final String ALIASES_PACKAGE = "com.spring.domain.*.dto";
private static final String MAPPER_RESOURCES = "classpath:mapper/**/*.xml";
private static final String MYBATIS_CONFIG = "primaryMybatisConfig";
private static final String SESSION_FACTORY = "primarySqlSessionFactory";
@Primary
@Bean(name = BEAN_NAME_GENERATOR)
MybatisBeanNameGenerator mybatisBeanNameGenerator() {
return new MybatisBeanNameGenerator(PrimaryDataSourceConfig.DATABASE);
}
@Primary
@Bean(name = MYBATIS_CONFIG)
org.apache.ibatis.session.Configuration mybatisConfiguration() {
var configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCallSettersOnNulls(true);
configuration.setReturnInstanceForEmptyRow(true);
configuration.setJdbcTypeForNull(JdbcType.NULL);
return configuration;
}
@Primary
@Bean(name = SESSION_FACTORY)
SqlSessionFactory sqlSessionFactory(@Qualifier(PrimaryDataSourceConfig.DATASOURCE) DataSource dataSource) throws Exception {
@@ -60,4 +41,13 @@ public class PrimaryMybatisConfig {
return sessionFactory.getObject();
}
private org.apache.ibatis.session.Configuration mybatisConfiguration() {
var configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCallSettersOnNulls(true);
configuration.setReturnInstanceForEmptyRow(true);
configuration.setJdbcTypeForNull(JdbcType.NULL);
return configuration;
}
}

View File

@@ -13,38 +13,21 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import com.spring.infra.db.SecondaryDataSourceConfig;
import com.spring.infra.db.orm.mybatis.util.MybatisBeanNameGenerator;
import com.spring.infra.db.orm.mybatis.annotation.SecondaryMapper;
@Configuration
@MapperScan(
basePackages = SecondaryMybatisConfig.BASE_PACKAGE,
basePackages = {SecondaryMybatisConfig.BASE_PACKAGE},
sqlSessionFactoryRef = SecondaryMybatisConfig.SESSION_FACTORY,
nameGenerator = MybatisBeanNameGenerator.class
annotationClass = SecondaryMapper.class
)
public class SecondaryMybatisConfig {
private static final String BEAN_NAME_GENERATOR = "secondaryNameGenerator";
private static final String BASE_PACKAGE = "com.spring.domain";
private static final String ALIASES_PACKAGE = "com.spring.domain.*.*.dto";
private static final String BASE_PACKAGE = "com.spring.domain.*.mapper";
private static final String ALIASES_PACKAGE = "com.spring.domain.*.dto";
private static final String MAPPER_RESOURCES = "classpath:mapper/**/*.xml";
private static final String MYBATIS_CONFIG = "secondaryMybatisConfig";
private static final String SESSION_FACTORY = "secondarySqlSessionFactory";
@Bean(name = BEAN_NAME_GENERATOR)
MybatisBeanNameGenerator mybatisBeanNameGenerator() {
return new MybatisBeanNameGenerator(SecondaryDataSourceConfig.DATABASE);
}
@Bean(name = MYBATIS_CONFIG)
org.apache.ibatis.session.Configuration mybatisConfiguration() {
var configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCallSettersOnNulls(true);
configuration.setReturnInstanceForEmptyRow(true);
configuration.setJdbcTypeForNull(JdbcType.NULL);
return configuration;
}
@Bean(name = SESSION_FACTORY)
SqlSessionFactory sqlSessionFactory(@Qualifier(SecondaryDataSourceConfig.DATASOURCE) DataSource dataSource) throws Exception {
var sessionFactory = new SqlSessionFactoryBean();
@@ -56,4 +39,13 @@ public class SecondaryMybatisConfig {
return sessionFactory.getObject();
}
private org.apache.ibatis.session.Configuration mybatisConfiguration() {
var configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCallSettersOnNulls(true);
configuration.setReturnInstanceForEmptyRow(true);
configuration.setJdbcTypeForNull(JdbcType.NULL);
return configuration;
}
}

View File

@@ -0,0 +1,12 @@
package com.spring.infra.db.orm.mybatis.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PrimaryMapper {
}

View File

@@ -0,0 +1,12 @@
package com.spring.infra.db.orm.mybatis.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SecondaryMapper {
}

View File

@@ -1,41 +0,0 @@
package com.spring.infra.db.orm.mybatis.util;
import java.util.Optional;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.lang.NonNull;
import com.spring.infra.db.annotation.DatabaseSelector;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class MybatisBeanNameGenerator extends AnnotationBeanNameGenerator {
private final String dbName;
@Override
@NonNull
public String generateBeanName(@NonNull BeanDefinition definition, @NonNull BeanDefinitionRegistry registry) {
return Optional.ofNullable(definition.getBeanClassName())
.flatMap(className -> {
try {
var beanClass = Class.forName(className);
var annotation = beanClass.getAnnotation(DatabaseSelector.class);
if (annotation == null) {
return Optional.of(super.generateBeanName(definition, registry));
}
if (dbName.equals(annotation.value())) {
return Optional.of(super.generateBeanName(definition, registry));
}
return Optional.empty();
} catch (ClassNotFoundException e) {
return Optional.empty();
}
})
.orElse("");
}
}

View File

@@ -2,6 +2,8 @@ package com.spring.infra.quartz;
import javax.sql.DataSource;
import org.quartz.Job;
import org.quartz.Scheduler;
import org.quartz.spi.JobFactory;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.batch.core.configuration.JobRegistry;
@@ -9,9 +11,7 @@ import org.springframework.batch.core.configuration.support.JobRegistryBeanPostP
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
import org.springframework.transaction.PlatformTransactionManager;
import lombok.RequiredArgsConstructor;
@@ -30,12 +30,12 @@ public class QuartzConfig {
* @param jobRegistry ths Spring Batch Job Registry
* @return JobRegistry BeanPostProcessor
*/
// @Bean
// JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
// var jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor();
// jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry);
// return jobRegistryBeanPostProcessor;
// }
@Bean
JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
var jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor();
jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry);
return jobRegistryBeanPostProcessor;
}
/**
* Quartz Schedule Job 에 의존성 주입
@@ -45,13 +45,10 @@ public class QuartzConfig {
*/
@Bean
JobFactory jobFactory(AutowireCapableBeanFactory beanFactory) {
return new SpringBeanJobFactory(){
@Override
protected @NonNull Object createJobInstance(@NonNull final TriggerFiredBundle bundle) throws Exception {
var job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
return (TriggerFiredBundle bundle, Scheduler scheduler) -> {
Job job = beanFactory.createBean(bundle.getJobDetail().getJobClass());
beanFactory.autowireBean(job);
return job;
};
}
@@ -64,12 +61,12 @@ public class QuartzConfig {
* @throws Exception the exception
*/
@Bean
SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory) throws Exception {
SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory) {
var factory = new SchedulerFactoryBean();
factory.setSchedulerName("SampleProject-0.0.1");
factory.setQuartzProperties(quartzProperties.toProperties());
factory.setOverwriteExistingJobs(true); //Job Detail 데이터 Overwrite 유무
factory.setDataSource(dataSource); //Schedule 관리를 Spring Datasource 에 위임
factory.setOverwriteExistingJobs(true);
factory.setDataSource(dataSource);
factory.setTransactionManager(transactionManager);
factory.setJobFactory(jobFactory);
factory.setAutoStartup(true);

View File

@@ -10,18 +10,16 @@ import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.springframework.batch.core.configuration.JobLocator;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
@Component
public class QuartzJobRegistrar implements BeanPostProcessor, ApplicationContextAware {
public class QuartzJobRegistrar implements ApplicationContextAware, InitializingBean {
private ApplicationContext applicationContext;
private Scheduler scheduler;
@@ -35,15 +33,20 @@ public class QuartzJobRegistrar implements BeanPostProcessor, ApplicationContext
}
@Override
public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
Class<?> beanClass = bean.getClass();
for (Method method : beanClass.getMethods()) {
QuartzJob quartzJobAnnotation = method.getAnnotation(QuartzJob.class);
if (quartzJobAnnotation != null) {
registerQuartzJob(quartzJobAnnotation, method.getName());
public void afterPropertiesSet() {
registerQuartzJobs();
}
private void registerQuartzJobs() {
applicationContext.getBeansWithAnnotation(QuartzJob.class).forEach((beanName, bean) -> {
Class<?> beanClass = bean.getClass();
for (Method method : beanClass.getMethods()) {
QuartzJob quartzJobAnnotation = method.getAnnotation(QuartzJob.class);
if (quartzJobAnnotation != null) {
registerQuartzJob(quartzJobAnnotation, method.getName());
}
}
}
return bean;
});
}
private void registerQuartzJob(QuartzJob quartzJobAnnotation, String jobName) {
@@ -52,16 +55,15 @@ public class QuartzJobRegistrar implements BeanPostProcessor, ApplicationContext
Trigger trigger = createTrigger(quartzJobAnnotation, jobDetail);
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
throw new RuntimeException("Error scheduling Quartz job", e);
throw new IllegalStateException("Error scheduling Quartz job", e);
}
}
private JobDetail createJobDetail(QuartzJob quartzJobAnnotation, String jobName) {
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("jobName", jobName);
jobDataMap.put("jobLauncher", applicationContext.getBean(JobLauncher.class));
jobDataMap.put("jobLocator", applicationContext.getBean(JobLocator.class));
jobDataMap.put("jobLauncher", applicationContext.getBean("jobLauncher"));
jobDataMap.put("jobLocator", applicationContext.getBean("jobLocator"));
return JobBuilder.newJob(QuartzJobLauncher.class)
.withIdentity(quartzJobAnnotation.name())
.setJobData(jobDataMap)

View File

@@ -0,0 +1,10 @@
<?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="com.spring.domain.user.mapper.AppUserMaper">
<select id="findByLoginId" resultType="com.spring.domain.user.entity.AppUser">
select *
from app_user
</select>
</mapper>