├── .gitignore ├── README.md ├── pom.xml └── src ├── main ├── java │ └── me │ │ └── jclagache │ │ └── data │ │ └── mybatis │ │ └── repository │ │ ├── MyBatisRepository.java │ │ ├── config │ │ ├── EnableMyBatisRepositories.java │ │ ├── MyBatisAutoConfiguration.java │ │ ├── MyBatisDataAutoConfiguration.java │ │ ├── MyBatisRepositoriesRegistrar.java │ │ ├── MyBatisRepositoryConfigExtension.java │ │ └── MyBatisRepositoryNamespaceHandler.java │ │ ├── core │ │ └── mapping │ │ │ ├── MyBatisPersistentEntity.java │ │ │ ├── MyBatisPersistentProperty.java │ │ │ ├── SimpleMyBatisMappingContext.java │ │ │ ├── SimpleMyBatisPersistentEntity.java │ │ │ └── SimpleMyBatisPersistentProperty.java │ │ ├── query │ │ ├── MyBatisQuery.java │ │ ├── MyBatisQueryLookupStrategy.java │ │ └── MyBatisQueryMethod.java │ │ └── support │ │ ├── MappingMyBatisEntityInformation.java │ │ ├── MyBatisEntityInformation.java │ │ ├── MyBatisRepositoryFactory.java │ │ ├── MyBatisRepositoryFactoryBean.java │ │ └── SimpleMyBatisRepository.java └── resources │ └── META-INF │ └── spring.factories └── test ├── java └── me │ └── jclagache │ └── data │ └── mybatis │ ├── ApplicationConfig.java │ ├── domain │ ├── AbstractEntity.java │ ├── Address.java │ ├── Customer.java │ └── EmailAddress.java │ ├── repository │ ├── CustomerRepository.java │ └── CustomerRepositoryTest.java │ └── rest │ ├── CustomerRestTest.java │ └── RestApplicationConfig.java └── resources ├── config └── application.properties ├── data.sql ├── logback.xml ├── mapper └── Customer │ └── Customer.xml └── schema.sql /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | *.iml 4 | .idea 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | > :warning: NOT MAINTAINED ANYMORE :warning: 3 | 4 | # Spring Data MyBatis # 5 | 6 | The primary goal of the [Spring Data](http://www.springsource.org/spring-data) project is to make it easier to build Spring-powered applications that use data access technologies. This module deals with enhanced support for [MyBatis](https://code.google.com/p/mybatis/) based data access layers. 7 | 8 | ## Features ## 9 | This project defines a `MyBatisRepository` base interface : 10 | 11 | ```java 12 | public interface MyBatisRepository extends Repository { 13 | T findOne(ID id); 14 | List findAll(); 15 | boolean exists(ID id); 16 | long count(); 17 | } 18 | ``` 19 | The only goal for now of this module is to make your MyBatis mappers created by [MyBatis-Spring](http://mybatis.github.io/spring/) : 20 | * implement these four methods by only providing one `select` statement 21 | * registered as Spring Data repositories 22 | 23 | 24 | ## Quick Start ## 25 | 26 | Build and deploy it into your local maven repository : 27 | 28 | ```bash 29 | git clone https://github.com/jclagache/spring-data-mybatis.git 30 | cd spring-data-mybatis 31 | mvn install 32 | ``` 33 | 34 | Add the jar to your maven project : 35 | 36 | ```xml 37 | 38 | me.jclagache 39 | spring-data-mybatis 40 | 0.1-SNAPSHOT 41 | 42 | ``` 43 | 44 | Configure your infrastructure: 45 | 1. add @EnableMyBatisRepositories annotation: 46 | ```java 47 | @Configuration 48 | @EnableMyBatisRepositories 49 | @EnableAutoConfiguration 50 | public class ApplicationConfig { 51 | 52 | } 53 | ``` 54 | 2. Configure datasource: 55 | 56 | ```properties 57 | 58 | spring.datasource.url=jdbc:mysql://mysql:3306/opentsp_user 59 | spring.datasource.username=admin 60 | spring.datasource.password=opentsp 61 | spring.datasource.test-on-borrow=true 62 | spring.datasource.test-while-idle=true 63 | spring.datasource.validation-query=SELECT 1; 64 | spring.datasource.driverClassName=com.mysql.jdbc.Driver 65 | 66 | #package for scanning mappers see https://mybatis.github.io/spring/mappers.html#scan default value * 67 | mybatis.mapper.base.package=me.jclagache.data.mybatis.repository 68 | #package mybatis aliases see https://mybatis.github.io/mybatis-3/configuration.html#typeAliases default value "" 69 | mybatis.aliases.package=me.jclagache.data.mybatis.domain 70 | 71 | ``` 72 | 73 | Create an entity: 74 | 75 | ```java 76 | public class User { 77 | 78 | private Integer id; 79 | private String firstname; 80 | private String lastname; 81 | // Getters and setters 82 | } 83 | ``` 84 | 85 | Create a repository interface: 86 | 87 | ```java 88 | public interface CustomerRepository extends MyBatisRepository { 89 | } 90 | ``` 91 | 92 | Write your mapper : 93 | 94 | ```xml 95 | 96 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 114 | 115 | ``` 116 | 117 | The `select` statement id must be named "find". 118 | The parameter type of the `select` statement is a `map`. 119 | This `map` has a key `pk` whose value is the object identifying this domain object. 120 | 121 | Write a test client 122 | 123 | ```java 124 | @RunWith(SpringJUnit4ClassRunner.class) 125 | @ContextConfiguration(classes = ApplicationConfig.class) 126 | public class UserRepositoryIntegrationTest { 127 | 128 | @Autowired UserRepository repository; 129 | 130 | @Test 131 | public void sampleTestCase() { 132 | Customer customer = customerRepository.findOne(100); 133 | assertNotNull(customer); 134 | List customers = customerRepository.findAll(); 135 | assertNotNull(customers); 136 | assertTrue(customers.size() > 0); 137 | } 138 | } 139 | ``` 140 | 141 | Transaction management 142 | 143 | see: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html#transaction-declarative-attransactional-settings 144 | 145 | See the test classes for more. 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | me.jclagache.data 5 | spring-data-mybatis 6 | jar 7 | 0.2-SNAPSHOT 8 | spring-data-mybatis 9 | https://github.com/jclagache/spring-data-mybatis 10 | 11 | 12 | https://github.com/jclagache/spring-data-mybatis 13 | scm:git:git@github.com:jclagache/spring-data-mybatis.git 14 | scm:git:git@github.com:jclagache/spring-data-mybatis.git 15 | 16 | 17 | 18 | 19 | 1.7 20 | 3.4.0 21 | 1.3.0 22 | 4.3.3.RELEASE 23 | 1.4.1.RELEASE 24 | 1.12.4.RELEASE 25 | 1.7.21 26 | 3.2.2 27 | 28 | 2.3.2 29 | 30 | 3.1 31 | 2.4 32 | 2.8.2 33 | 2.9 34 | 35 | 36 | 37 | 38 | org.springframework.data 39 | spring-data-commons 40 | ${spring-data-commons.version} 41 | 42 | 43 | 44 | org.springframework 45 | spring-orm 46 | ${spring.version} 47 | 48 | 49 | 50 | org.springframework 51 | spring-context 52 | ${spring.version} 53 | 54 | 55 | 56 | org.springframework 57 | spring-aop 58 | ${spring.version} 59 | 60 | 61 | 62 | org.springframework 63 | spring-tx 64 | ${spring.version} 65 | 66 | 67 | 68 | org.springframework 69 | spring-beans 70 | ${spring.version} 71 | 72 | 73 | 74 | org.springframework 75 | spring-instrument 76 | ${spring.version} 77 | provided 78 | 79 | 80 | 81 | org.springframework.boot 82 | spring-boot-starter-jdbc 83 | ${spring-boot.version} 84 | 85 | 86 | 87 | org.springframework 88 | spring-core 89 | ${spring.version} 90 | 91 | 92 | commons-logging 93 | commons-logging 94 | 95 | 96 | 97 | 98 | 99 | org.springframework 100 | spring-aspects 101 | ${spring.version} 102 | true 103 | 104 | 105 | org.mybatis 106 | mybatis 107 | ${mybatis.version} 108 | 109 | 110 | org.mybatis 111 | mybatis-spring 112 | ${mybatis-spring.version} 113 | 114 | 115 | 116 | 117 | org.springframework.boot 118 | spring-boot-starter-test 119 | ${spring-boot.version} 120 | test 121 | 122 | 123 | org.hsqldb 124 | hsqldb 125 | ${hsqldb.version} 126 | test 127 | 128 | 129 | 130 | 131 | org.springframework.boot 132 | spring-boot-starter-data-rest 133 | ${spring-boot.version} 134 | test 135 | 136 | 137 | 138 | 139 | org.slf4j 140 | jcl-over-slf4j 141 | ${slf4j.version} 142 | runtime 143 | 144 | 145 | org.springframework.boot 146 | spring-boot-autoconfigure 147 | ${spring-boot.version} 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | org.apache.maven.plugins 156 | maven-dependency-plugin 157 | ${maven-dependency-plugin.version} 158 | 159 | 160 | 161 | org.apache.maven.plugins 162 | maven-compiler-plugin 163 | ${maven-compiler-plugin.version} 164 | 165 | ${source.level} 166 | ${source.level} 167 | 168 | 169 | 170 | 171 | maven-source-plugin 172 | ${maven-source-plugin.version} 173 | 174 | 175 | attach-sources 176 | 177 | jar 178 | 179 | 180 | 181 | 182 | 183 | 184 | org.apache.maven.plugins 185 | maven-deploy-plugin 186 | ${maven-deploy-plugin.version} 187 | 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/MyBatisRepository.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository; 2 | 3 | import org.springframework.data.repository.NoRepositoryBean; 4 | import org.springframework.data.repository.Repository; 5 | 6 | import java.io.Serializable; 7 | import java.util.List; 8 | 9 | /** 10 | * MyBatis specific extension of {@link org.springframework.data.repository.Repository}. 11 | * 12 | * @author Jean-Christophe Lagache 13 | */ 14 | @NoRepositoryBean 15 | public interface MyBatisRepository extends Repository { 16 | 17 | /** 18 | * Retrieves an entity by its id. 19 | * 20 | * @param id must not be {@literal null}. 21 | * @return the entity with the given id or {@literal null} if none found 22 | * @throws IllegalArgumentException if {@code id} is {@literal null} 23 | */ 24 | T findOne(ID id); 25 | 26 | /** 27 | * Returns all instances of the type. 28 | * 29 | * @return all entities 30 | */ 31 | List findAll(); 32 | 33 | /** 34 | * Returns whether an entity with the given id exists. 35 | * 36 | * @param id must not be {@literal null}. 37 | * @return true if an entity with the given id exists, {@literal false} otherwise 38 | * @throws IllegalArgumentException if {@code id} is {@literal null} 39 | */ 40 | boolean exists(ID id); 41 | 42 | /** 43 | * Returns the number of entities available. 44 | * 45 | * @return the number of entities 46 | */ 47 | long count(); 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/config/EnableMyBatisRepositories.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.config; 2 | 3 | import org.springframework.context.annotation.ComponentScan.Filter; 4 | import org.springframework.context.annotation.Import; 5 | import me.jclagache.data.mybatis.repository.support.MyBatisRepositoryFactoryBean; 6 | import org.springframework.data.repository.query.QueryLookupStrategy.Key; 7 | import org.springframework.transaction.PlatformTransactionManager; 8 | 9 | import java.lang.annotation.*; 10 | 11 | /** 12 | * Annotation to enable MyBatis repositories. Will scan the package of the 13 | * annotated configuration class for Spring Data repositories by default. 14 | * 15 | * @author Jean-Christophe Lagache 16 | */ 17 | @Target(ElementType.TYPE) 18 | @Retention(RetentionPolicy.RUNTIME) 19 | @Documented 20 | @Inherited 21 | @Import({MyBatisRepositoriesRegistrar.class, MyBatisAutoConfiguration.class}) 22 | public @interface EnableMyBatisRepositories { 23 | /** 24 | * Alias for the {@link #basePackages()} attribute. Allows for more concise 25 | * annotation declarations e.g.: 26 | * {@code @EnableMyBatisRepositories("org.my.pkg")} instead of 27 | * {@code @EnableMyBatisRepositories(basePackages="org.my.pkg")}. 28 | */ 29 | String[] value() default {}; 30 | 31 | /** 32 | * Base packages to scan for annotated components. {@link #value()} is an 33 | * alias for (and mutually exclusive with) this attribute. Use 34 | * {@link #basePackageClasses()} for a type-safe alternative to String-based 35 | * package names. 36 | */ 37 | String[] basePackages() default {}; 38 | 39 | /** 40 | * Type-safe alternative to {@link #basePackages()} for specifying the 41 | * packages to scan for annotated components. The package of each class 42 | * specified will be scanned. Consider creating a special no-op marker class 43 | * or interface in each package that serves no purpose other than being 44 | * referenced by this attribute. 45 | */ 46 | Class[] basePackageClasses() default {}; 47 | 48 | /** 49 | * Specifies which types are eligible for component scanning. Further 50 | * narrows the set of candidate components from everything in 51 | * {@link #basePackages()} to everything in the base packages that matches 52 | * the given filter or filters. 53 | */ 54 | Filter[] includeFilters() default {}; 55 | 56 | /** 57 | * Specifies which types are not eligible for component scanning. 58 | */ 59 | Filter[] excludeFilters() default {}; 60 | 61 | /** 62 | * Returns the postfix to be used when looking up custom repository 63 | * implementations. Defaults to {@literal Impl}. So for a repository named 64 | * {@code PersonRepository} the corresponding implementation class will be 65 | * looked up scanning for {@code PersonRepositoryImpl}. 66 | * 67 | * @return 68 | */ 69 | String repositoryImplementationPostfix() default "Impl"; 70 | 71 | /** 72 | * Configures the location of where to find the Spring Data named queries properties file. Will default to 73 | * {@code META-INFO/jpa-named-queries.properties}. 74 | * 75 | * @return 76 | */ 77 | String namedQueriesLocation() default ""; 78 | 79 | /** 80 | * Returns the key of the {@link QueryLookupStrategy} to be used for lookup 81 | * queries for query methods. Defaults to {@link Key#USE_DECLARED_QUERY}. 82 | * 83 | * @return 84 | */ 85 | Key queryLookupStrategy() default Key.USE_DECLARED_QUERY; 86 | 87 | /** 88 | * Returns the {@link FactoryBean} class to be used for each repository 89 | * instance. Defaults to {@link MyBatisRepositoryFactoryBean}. 90 | * 91 | * @return 92 | */ 93 | Class repositoryFactoryBeanClass() default MyBatisRepositoryFactoryBean.class; 94 | 95 | String sqlSessionTemplateRef() default "sqlSessionTemplate"; 96 | 97 | String mappingContextRef() default "myBatisMappingContext"; 98 | 99 | /** 100 | * /** Configures the name of the {@link PlatformTransactionManager} bean 101 | * definition to be used to create repositories discovered through this 102 | * annotation. Defaults to {@code transactionManager}. 103 | * 104 | * @return 105 | */ 106 | String transactionManagerRef() default "transactionManager"; 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/config/MyBatisAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.config; 2 | 3 | import me.jclagache.data.mybatis.repository.MyBatisRepository; 4 | import org.apache.ibatis.session.SqlSessionFactory; 5 | import org.mybatis.spring.SqlSessionFactoryBean; 6 | import org.mybatis.spring.SqlSessionTemplate; 7 | import org.mybatis.spring.mapper.MapperScannerConfigurer; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.core.io.Resource; 13 | import org.springframework.core.io.ResourceLoader; 14 | import org.springframework.core.io.support.ResourcePatternResolver; 15 | import org.springframework.core.io.support.ResourcePatternUtils; 16 | import org.springframework.jdbc.datasource.DataSourceTransactionManager; 17 | import org.springframework.transaction.PlatformTransactionManager; 18 | import org.springframework.transaction.annotation.EnableTransactionManagement; 19 | 20 | import javax.sql.DataSource; 21 | import java.io.IOException; 22 | 23 | /** 24 | * 25 | *Configuration of MyBats: 26 | * 27 | * SqlSessionFactory see: https://mybatis.github.io/mybatis-3/getting-started.html 28 | * MapperScannerConfigurer see: https://mybatis.github.io/spring/mappers.html#scan 29 | * SqlSessionTemplate see https://mybatis.github.io/spring/sqlsession.html 30 | * Spring TransactionManager 31 | * 32 | */ 33 | 34 | @Configuration 35 | @EnableTransactionManagement 36 | public class MyBatisAutoConfiguration { 37 | 38 | @Bean 39 | public static MapperScannerConfigurer mapperScannerConfigurer(@Value("${mybatis.mapper.base.package:*}") String basePackage) { 40 | MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); 41 | mapperScannerConfigurer.setMarkerInterface(MyBatisRepository.class); 42 | mapperScannerConfigurer.setSqlSessionTemplateBeanName("sqlSessionTemplate"); 43 | mapperScannerConfigurer.setBasePackage(basePackage); 44 | return mapperScannerConfigurer; 45 | } 46 | 47 | @Bean 48 | @Autowired 49 | public SqlSessionFactory sqlSessionFactory(DataSource dataSource, ResourceLoader resourceLoader, @Value("${mybatis.aliases.package:}") String aliases) throws Exception { 50 | SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); 51 | sessionFactory.setDataSource(dataSource); 52 | sessionFactory.setTypeAliasesPackage(aliases); 53 | sessionFactory.setMapperLocations(getResources(resourceLoader, "classpath*:mapper/**/*.xml")); 54 | return sessionFactory.getObject(); 55 | } 56 | 57 | @Bean 58 | @Autowired 59 | SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception { 60 | return new SqlSessionTemplate(sqlSessionFactory); 61 | } 62 | 63 | @Bean 64 | @Autowired 65 | PlatformTransactionManager transactionManager(DataSource dataSource) throws Exception { 66 | return new DataSourceTransactionManager(dataSource); 67 | } 68 | 69 | /** 70 | * Method which loads resources by packagePath 71 | * @param resourceLoader 72 | * @param packagePath 73 | * @return 74 | * @throws IOException 75 | */ 76 | private Resource[] getResources(ResourceLoader resourceLoader, String packagePath) throws IOException { 77 | ResourcePatternResolver resourceResolver = ResourcePatternUtils 78 | .getResourcePatternResolver(resourceLoader); 79 | return resourceResolver.getResources(packagePath); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/config/MyBatisDataAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.config; 2 | 3 | import me.jclagache.data.mybatis.repository.core.mapping.SimpleMyBatisMappingContext; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | public class MyBatisDataAutoConfiguration { 9 | 10 | @Bean 11 | public SimpleMyBatisMappingContext myBatisMappingContext() { 12 | return new SimpleMyBatisMappingContext(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/config/MyBatisRepositoriesRegistrar.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.config; 2 | 3 | import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; 4 | import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport; 5 | import org.springframework.data.repository.config.RepositoryConfigurationExtension; 6 | 7 | import java.lang.annotation.Annotation; 8 | 9 | /** 10 | * {@link ImportBeanDefinitionRegistrar} to enable {@link EnableMyBatisRepositories} annotation. 11 | * 12 | * @author Jean-Christophe Lagache 13 | */ 14 | public class MyBatisRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport { 15 | 16 | @Override 17 | protected Class getAnnotation() { 18 | return EnableMyBatisRepositories.class; 19 | } 20 | 21 | @Override 22 | protected RepositoryConfigurationExtension getExtension() { 23 | return new MyBatisRepositoryConfigExtension(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/config/MyBatisRepositoryConfigExtension.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.config; 2 | 3 | import me.jclagache.data.mybatis.repository.core.mapping.SimpleMyBatisMappingContext; 4 | import me.jclagache.data.mybatis.repository.support.MyBatisRepositoryFactoryBean; 5 | import org.springframework.beans.factory.support.BeanDefinitionBuilder; 6 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 7 | import org.springframework.beans.factory.support.RootBeanDefinition; 8 | import org.springframework.core.annotation.AnnotationAttributes; 9 | import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource; 10 | import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport; 11 | import org.springframework.data.repository.config.RepositoryConfigurationSource; 12 | import org.springframework.data.repository.config.XmlRepositoryConfigurationSource; 13 | import org.springframework.util.StringUtils; 14 | import org.w3c.dom.Element; 15 | 16 | 17 | /** 18 | * Implementation of {@link org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport} for supporting mybatis 19 | * allows to use read EnableMyBatisRepositories annotation for configuring repository bean 20 | * 21 | */ 22 | public class MyBatisRepositoryConfigExtension extends RepositoryConfigurationExtensionSupport { 23 | 24 | @Override 25 | public String getRepositoryFactoryClassName() { 26 | return MyBatisRepositoryFactoryBean.class.getName(); 27 | } 28 | 29 | @Override 30 | protected String getModulePrefix() { 31 | return "mybatis"; 32 | } 33 | 34 | /* 35 | * (non-Javadoc) 36 | * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#registerBeansForRoot(org.springframework.beans.factory.support.BeanDefinitionRegistry, org.springframework.data.repository.config.RepositoryConfigurationSource) 37 | */ 38 | @Override 39 | public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConfigurationSource configurationSource) { 40 | 41 | super.registerBeansForRoot(registry, configurationSource); 42 | 43 | String attribute = configurationSource.getAttribute("mappingContextRef"); 44 | 45 | if (!StringUtils.hasText(attribute)) { 46 | registry.registerBeanDefinition(String.format("%1$s.%2$s", SimpleMyBatisMappingContext.class.getName(), "DEFAULT"), 47 | new RootBeanDefinition(SimpleMyBatisMappingContext.class)); 48 | } 49 | } 50 | 51 | @Override 52 | public void postProcess(BeanDefinitionBuilder builder, XmlRepositoryConfigurationSource config) { 53 | 54 | Element element = config.getElement(); 55 | 56 | postProcess(builder, element.getAttribute("transaction-manager-ref"), 57 | element.getAttribute("sql-session-template-ref"), element.getAttribute("mapping-context-ref"), config.getSource()); 58 | } 59 | 60 | /* 61 | * (non-Javadoc) 62 | * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource) 63 | */ 64 | @Override 65 | public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) { 66 | 67 | AnnotationAttributes attributes = config.getAttributes(); 68 | 69 | postProcess(builder, attributes.getString("transactionManagerRef"), 70 | attributes.getString("sqlSessionTemplateRef"), attributes.getString("mappingContextRef"), config.getSource()); 71 | } 72 | 73 | private void postProcess(BeanDefinitionBuilder builder, String transactionManagerRef, String sqlSessionTemplateRef, String mappingContextRef, 74 | Object source) { 75 | 76 | //TODO : transactionManagerRef 77 | 78 | if (StringUtils.hasText(sqlSessionTemplateRef)) { 79 | builder.addPropertyReference("sqlSessionTemplate", sqlSessionTemplateRef); 80 | } 81 | else { 82 | builder.addPropertyReference("sqlSessionTemplate","sqlSessionTemplate"); 83 | } 84 | if (StringUtils.hasText(mappingContextRef)) { 85 | builder.addPropertyReference("myBatisMappingContext", mappingContextRef); 86 | } 87 | else { 88 | builder.addPropertyReference("myBatisMappingContext",String.format("%1$s.%2$s", SimpleMyBatisMappingContext.class.getName(), "DEFAULT")); 89 | } 90 | 91 | } 92 | 93 | 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/config/MyBatisRepositoryNamespaceHandler.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.config; 2 | 3 | import org.springframework.beans.factory.xml.NamespaceHandlerSupport; 4 | import org.springframework.data.repository.config.RepositoryBeanDefinitionParser; 5 | import org.springframework.data.repository.config.RepositoryConfigurationExtension; 6 | 7 | public class MyBatisRepositoryNamespaceHandler extends NamespaceHandlerSupport { 8 | 9 | @Override 10 | public void init() { 11 | RepositoryConfigurationExtension extension = new MyBatisRepositoryConfigExtension(); 12 | RepositoryBeanDefinitionParser repositoryBeanDefinitionParser = new RepositoryBeanDefinitionParser(extension); 13 | registerBeanDefinitionParser("repositories", repositoryBeanDefinitionParser); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/core/mapping/MyBatisPersistentEntity.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.core.mapping; 2 | 3 | import org.springframework.data.mapping.PersistentEntity; 4 | 5 | public interface MyBatisPersistentEntity extends PersistentEntity { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/core/mapping/MyBatisPersistentProperty.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.core.mapping; 2 | 3 | 4 | import org.springframework.data.mapping.PersistentProperty; 5 | 6 | public interface MyBatisPersistentProperty extends PersistentProperty { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/core/mapping/SimpleMyBatisMappingContext.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.core.mapping; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.data.mapping.context.AbstractMappingContext; 7 | import org.springframework.data.mapping.model.SimpleTypeHolder; 8 | import org.springframework.data.util.TypeInformation; 9 | 10 | import java.beans.PropertyDescriptor; 11 | import java.lang.reflect.Field; 12 | 13 | public class SimpleMyBatisMappingContext extends 14 | AbstractMappingContext, MyBatisPersistentProperty> implements ApplicationContextAware { 15 | 16 | /** 17 | * Contains the application context to configure the application. 18 | */ 19 | private ApplicationContext context; 20 | 21 | @Override 22 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 23 | context = applicationContext; 24 | } 25 | 26 | @Override 27 | protected SimpleMyBatisPersistentEntity createPersistentEntity(TypeInformation typeInformation) { 28 | SimpleMyBatisPersistentEntity entity = new SimpleMyBatisPersistentEntity<>(typeInformation); 29 | if (context != null) { 30 | entity.setApplicationContext(context); 31 | } 32 | return entity; 33 | 34 | } 35 | 36 | @Override 37 | protected MyBatisPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, SimpleMyBatisPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { 38 | return new SimpleMyBatisPersistentProperty(field, descriptor, owner, simpleTypeHolder); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/core/mapping/SimpleMyBatisPersistentEntity.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.core.mapping; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.context.expression.BeanFactoryAccessor; 7 | import org.springframework.context.expression.BeanFactoryResolver; 8 | import org.springframework.data.mapping.model.BasicPersistentEntity; 9 | import org.springframework.data.util.TypeInformation; 10 | import org.springframework.expression.spel.support.StandardEvaluationContext; 11 | 12 | public class SimpleMyBatisPersistentEntity extends BasicPersistentEntity 13 | implements MyBatisPersistentEntity, ApplicationContextAware { 14 | 15 | private final StandardEvaluationContext context; 16 | 17 | public SimpleMyBatisPersistentEntity(TypeInformation information) { 18 | super(information); 19 | this.context = new StandardEvaluationContext(); 20 | } 21 | 22 | @Override 23 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 24 | context.addPropertyAccessor(new BeanFactoryAccessor()); 25 | context.setBeanResolver(new BeanFactoryResolver(applicationContext)); 26 | context.setRootObject(applicationContext); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/core/mapping/SimpleMyBatisPersistentProperty.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.core.mapping; 2 | 3 | import org.springframework.data.mapping.Association; 4 | import org.springframework.data.mapping.PersistentEntity; 5 | import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; 6 | import org.springframework.data.mapping.model.SimpleTypeHolder; 7 | 8 | import java.beans.PropertyDescriptor; 9 | import java.lang.reflect.Field; 10 | 11 | public class SimpleMyBatisPersistentProperty extends AnnotationBasedPersistentProperty 12 | implements MyBatisPersistentProperty{ 13 | 14 | /** 15 | * Creates a new {@link AnnotationBasedPersistentProperty}. 16 | * 17 | * @param field must not be {@literal null}. 18 | * @param propertyDescriptor can be {@literal null}. 19 | * @param owner must not be {@literal null}. 20 | * @param simpleTypeHolder 21 | */ 22 | public SimpleMyBatisPersistentProperty(Field field, PropertyDescriptor propertyDescriptor, PersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { 23 | super(field, propertyDescriptor, owner, simpleTypeHolder); 24 | } 25 | 26 | @Override 27 | protected Association createAssociation() { 28 | return new Association(this, null); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/query/MyBatisQuery.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.query; 2 | 3 | import org.mybatis.spring.SqlSessionTemplate; 4 | import org.springframework.data.repository.query.QueryMethod; 5 | import org.springframework.data.repository.query.RepositoryQuery; 6 | import org.springframework.util.Assert; 7 | import org.springframework.util.ReflectionUtils; 8 | 9 | /** 10 | * Binds spring Repository methods with mybatis mapper 11 | */ 12 | public class MyBatisQuery implements RepositoryQuery { 13 | 14 | private final MyBatisQueryMethod queryMethod; 15 | private final SqlSessionTemplate sessionTemplate; 16 | 17 | public MyBatisQuery(MyBatisQueryMethod queryMethod, SqlSessionTemplate sessionTemplate) { 18 | Assert.notNull(queryMethod); 19 | Assert.notNull(sessionTemplate); 20 | this.queryMethod = queryMethod; 21 | this.sessionTemplate = sessionTemplate; 22 | } 23 | 24 | @Override 25 | public Object execute(Object[] parameters) { 26 | return ReflectionUtils 27 | .invokeMethod(queryMethod.getMethod(), sessionTemplate.getMapper(queryMethod.getRepositoryInterface()), parameters); 28 | } 29 | 30 | @Override 31 | public QueryMethod getQueryMethod() { 32 | return queryMethod; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/query/MyBatisQueryLookupStrategy.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.query; 2 | 3 | import org.mybatis.spring.SqlSessionTemplate; 4 | import org.springframework.data.projection.ProjectionFactory; 5 | import org.springframework.data.repository.core.NamedQueries; 6 | import org.springframework.data.repository.core.RepositoryMetadata; 7 | import org.springframework.data.repository.query.QueryLookupStrategy; 8 | import org.springframework.data.repository.query.QueryLookupStrategy.Key; 9 | import org.springframework.data.repository.query.RepositoryQuery; 10 | 11 | import java.lang.reflect.Method; 12 | 13 | /** 14 | * 15 | * TODO: add support @Query annotation 16 | */ 17 | public class MyBatisQueryLookupStrategy { 18 | 19 | /** 20 | * Private constructor to prevent instantiation. 21 | */ 22 | private MyBatisQueryLookupStrategy() { 23 | } 24 | 25 | 26 | private static class DeclaredQueryLookupStrategy implements QueryLookupStrategy { 27 | 28 | private final SqlSessionTemplate sessionTemplate; 29 | 30 | public DeclaredQueryLookupStrategy(SqlSessionTemplate sessionTemplate) { 31 | this.sessionTemplate = sessionTemplate; 32 | } 33 | 34 | @Override 35 | public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, 36 | NamedQueries namedQueries) { 37 | return resolveQuery(new MyBatisQueryMethod(method, metadata, factory), sessionTemplate, namedQueries); 38 | } 39 | 40 | protected RepositoryQuery resolveQuery(MyBatisQueryMethod method, SqlSessionTemplate sessionTemplate, NamedQueries namedQueries) { 41 | return new MyBatisQuery(method, sessionTemplate); 42 | } 43 | } 44 | 45 | /** 46 | * Creates a {@link QueryLookupStrategy} for the given {@link SqlSessionTemplate} and {@link Key}. 47 | * 48 | * @param sessionTemplate 49 | * @param key 50 | * @return 51 | */ 52 | public static QueryLookupStrategy create(SqlSessionTemplate sessionTemplate, Key key) { 53 | if (key == null) { 54 | return new DeclaredQueryLookupStrategy(sessionTemplate); 55 | } 56 | if(Key.USE_DECLARED_QUERY.equals(key)) { 57 | return new DeclaredQueryLookupStrategy(sessionTemplate); 58 | } 59 | else { 60 | throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key)); 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/query/MyBatisQueryMethod.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.query; 2 | 3 | import org.springframework.data.projection.ProjectionFactory; 4 | import org.springframework.data.repository.core.RepositoryMetadata; 5 | import org.springframework.data.repository.query.QueryMethod; 6 | 7 | import java.lang.reflect.Method; 8 | 9 | public class MyBatisQueryMethod extends QueryMethod { 10 | 11 | private final Class mapperInterface; 12 | private final Method method; 13 | 14 | public MyBatisQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory) { 15 | super(method, metadata, factory); 16 | this.method = method; 17 | mapperInterface = metadata.getRepositoryInterface(); 18 | } 19 | 20 | public String getMappedStatementId() { 21 | return mapperInterface.getName() + "." + method.getName(); 22 | } 23 | 24 | public Class getRepositoryInterface() { 25 | return mapperInterface; 26 | } 27 | 28 | public Method getMethod() { 29 | return method; 30 | } 31 | 32 | public String getNamedQueryName() { 33 | return null; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/support/MappingMyBatisEntityInformation.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.support; 2 | 3 | import me.jclagache.data.mybatis.repository.core.mapping.MyBatisPersistentEntity; 4 | import org.springframework.data.repository.core.support.PersistentEntityInformation; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * Created by jclagache on 09/11/2016. 10 | */ 11 | public class MappingMyBatisEntityInformation extends PersistentEntityInformation implements MyBatisEntityInformation{ 12 | 13 | private final MyBatisPersistentEntity entity; 14 | 15 | /** 16 | * Creates a new {@link MappingMyBatisEntityInformation} for the given {@link MyBatisPersistentEntity}. 17 | * 18 | * @param entity must not be {@literal null}. 19 | */ 20 | public MappingMyBatisEntityInformation(MyBatisPersistentEntity entity) { 21 | super(entity); 22 | this.entity = entity; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/support/MyBatisEntityInformation.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.support; 2 | 3 | import org.springframework.data.repository.core.EntityInformation; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * Marker interface for the MyBatis Entity Information. 9 | * 10 | * @author Jean-Christophe Lagache 11 | */ 12 | public interface MyBatisEntityInformation extends EntityInformation { 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/support/MyBatisRepositoryFactory.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.support; 2 | 3 | import me.jclagache.data.mybatis.repository.MyBatisRepository; 4 | import me.jclagache.data.mybatis.repository.core.mapping.MyBatisPersistentEntity; 5 | import me.jclagache.data.mybatis.repository.core.mapping.MyBatisPersistentProperty; 6 | import me.jclagache.data.mybatis.repository.query.MyBatisQueryLookupStrategy; 7 | import org.mybatis.spring.SqlSessionTemplate; 8 | import org.springframework.data.mapping.context.MappingContext; 9 | import org.springframework.data.repository.core.RepositoryInformation; 10 | import org.springframework.data.repository.core.RepositoryMetadata; 11 | import org.springframework.data.repository.core.support.RepositoryFactorySupport; 12 | import org.springframework.data.repository.query.EvaluationContextProvider; 13 | import org.springframework.data.repository.query.QueryLookupStrategy; 14 | import org.springframework.data.repository.query.QueryLookupStrategy.Key; 15 | import org.springframework.util.Assert; 16 | 17 | import java.io.Serializable; 18 | 19 | public class MyBatisRepositoryFactory extends RepositoryFactorySupport { 20 | 21 | private final SqlSessionTemplate sessionTemplate; 22 | private final MappingContext, 23 | MyBatisPersistentProperty> context; 24 | 25 | public MyBatisRepositoryFactory(SqlSessionTemplate sessionTemplate, MappingContext, 26 | MyBatisPersistentProperty> context) { 27 | super(); 28 | Assert.notNull(sessionTemplate, "SqlSessionTemplate must not be null!"); 29 | Assert.notNull(context, "MappingContext must not be null!"); 30 | this.sessionTemplate = sessionTemplate; 31 | this.context = context; 32 | } 33 | 34 | @Override 35 | @SuppressWarnings("unchecked") 36 | public MyBatisEntityInformation getEntityInformation( 37 | Class domainClass) { 38 | MyBatisPersistentEntity entity = (MyBatisPersistentEntity) context.getPersistentEntity(domainClass); 39 | return new MappingMyBatisEntityInformation(entity); 40 | } 41 | 42 | @Override 43 | protected Object getTargetRepository(RepositoryInformation repositoryInformation) { 44 | return new SimpleMyBatisRepository(sessionTemplate, repositoryInformation.getRepositoryInterface().getCanonicalName()); 45 | } 46 | 47 | @Override 48 | protected Class getRepositoryBaseClass(RepositoryMetadata repositoryMetadata) { 49 | return MyBatisRepository.class; 50 | } 51 | 52 | 53 | @Override 54 | protected QueryLookupStrategy getQueryLookupStrategy(Key key, EvaluationContextProvider evaluationContextProvider) { 55 | return MyBatisQueryLookupStrategy.create(sessionTemplate, key); 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/support/MyBatisRepositoryFactoryBean.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.support; 2 | 3 | import me.jclagache.data.mybatis.repository.core.mapping.MyBatisPersistentEntity; 4 | import me.jclagache.data.mybatis.repository.core.mapping.MyBatisPersistentProperty; 5 | import org.mybatis.spring.SqlSessionTemplate; 6 | import org.springframework.data.mapping.context.MappingContext; 7 | import org.springframework.data.repository.Repository; 8 | import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; 9 | import org.springframework.data.repository.core.support.RepositoryFactorySupport; 10 | import org.springframework.util.Assert; 11 | 12 | import java.io.Serializable; 13 | 14 | public class MyBatisRepositoryFactoryBean, S, ID extends Serializable> 15 | extends RepositoryFactoryBeanSupport { 16 | 17 | private SqlSessionTemplate sqlSessionTemplate; 18 | private MappingContext, MyBatisPersistentProperty> context; 19 | 20 | public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { 21 | this.sqlSessionTemplate = sqlSessionTemplate; 22 | } 23 | 24 | public void setMyBatisMappingContext(MappingContext, MyBatisPersistentProperty> mappingContext) { 25 | super.setMappingContext(mappingContext); 26 | this.context = mappingContext; 27 | } 28 | 29 | @Override 30 | protected RepositoryFactorySupport createRepositoryFactory() { 31 | return new MyBatisRepositoryFactory(sqlSessionTemplate, context); 32 | } 33 | 34 | @Override 35 | public void afterPropertiesSet() { 36 | Assert.notNull(sqlSessionTemplate, "SqlSessionTemplate must not be null!"); 37 | Assert.notNull(context, "MyBatisMappingContext must not be null!"); 38 | super.afterPropertiesSet(); 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/me/jclagache/data/mybatis/repository/support/SimpleMyBatisRepository.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository.support; 2 | 3 | import me.jclagache.data.mybatis.repository.MyBatisRepository; 4 | import org.mybatis.spring.SqlSessionTemplate; 5 | import org.springframework.util.Assert; 6 | 7 | import java.io.Serializable; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * 14 | * Specifies Default binding for findOne, findAll, exists, count methods 15 | * between org.springframework.data.mybatis.repository.MyBatisRepository and mybatis mapper 16 | */ 17 | public class SimpleMyBatisRepository implements MyBatisRepository { 18 | 19 | private final SqlSessionTemplate sessionTemplate; 20 | private final String mappedStatementId; 21 | 22 | public SimpleMyBatisRepository(SqlSessionTemplate sessionTemplate, String mappedStatementNamespace) { 23 | Assert.notNull(sessionTemplate, "SqlSessionTemplate must not be null!"); 24 | Assert.notNull(mappedStatementNamespace, "mappedStatementNamespace must not be null!"); 25 | this.sessionTemplate = sessionTemplate; 26 | this.mappedStatementId = mappedStatementNamespace + ".find"; 27 | } 28 | 29 | @Override 30 | public T findOne(ID id) { 31 | Map params = new HashMap<>(); 32 | params.put("pk", id); 33 | return sessionTemplate.selectOne(mappedStatementId, params); 34 | } 35 | 36 | @Override 37 | public List findAll() { 38 | Map params = new HashMap<>(); 39 | return sessionTemplate.selectList(mappedStatementId, params); 40 | } 41 | 42 | @Override 43 | public boolean exists(ID id) { 44 | return findOne(id) != null; 45 | } 46 | 47 | @Override 48 | public long count() { 49 | return findAll().size(); 50 | } 51 | 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | me.jclagache.data.mybatis.repository.config.MyBatisAutoConfiguration,\ 3 | me.jclagache.data.mybatis.repository.config.MyBatisDataAutoConfiguration -------------------------------------------------------------------------------- /src/test/java/me/jclagache/data/mybatis/ApplicationConfig.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis; 2 | 3 | import me.jclagache.data.mybatis.repository.config.EnableMyBatisRepositories; 4 | import me.jclagache.data.mybatis.repository.config.MyBatisAutoConfiguration; 5 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | @EnableMyBatisRepositories 10 | @EnableAutoConfiguration 11 | public class ApplicationConfig extends MyBatisAutoConfiguration { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/me/jclagache/data/mybatis/domain/AbstractEntity.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.domain; 2 | 3 | import org.springframework.data.annotation.Id; 4 | 5 | public class AbstractEntity { 6 | @Id 7 | private Integer id; 8 | 9 | public Integer getId() { 10 | return id; 11 | } 12 | 13 | public void setId(Integer id) { 14 | this.id = id; 15 | } 16 | 17 | @Override 18 | public boolean equals(Object o) { 19 | if (this == o) return true; 20 | if (o == null || getClass() != o.getClass()) return false; 21 | AbstractEntity that = (AbstractEntity) o; 22 | return !(id != null ? !id.equals(that.id) : that.id != null); 23 | 24 | } 25 | 26 | @Override 27 | public int hashCode() { 28 | return id != null ? id.hashCode() : 0; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/me/jclagache/data/mybatis/domain/Address.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.domain; 2 | 3 | public class Address extends AbstractEntity { 4 | private String street, city, country; 5 | 6 | public Address() { 7 | } 8 | 9 | public Address(String street, String city, String country) { 10 | this.street = street; 11 | this.city = city; 12 | this.country = country; 13 | } 14 | 15 | public String getCountry() { 16 | return country; 17 | } 18 | 19 | public void setCountry(String country) { 20 | this.country = country; 21 | } 22 | 23 | public String getStreet() { 24 | return street; 25 | } 26 | 27 | public void setStreet(String street) { 28 | this.street = street; 29 | } 30 | 31 | public String getCity() { 32 | return city; 33 | } 34 | 35 | public void setCity(String city) { 36 | this.city = city; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return street + ", " + city + " " + country ; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/me/jclagache/data/mybatis/domain/Customer.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.domain; 2 | 3 | import java.util.List; 4 | 5 | public class Customer extends AbstractEntity { 6 | private String firstName; 7 | private String lastName; 8 | private EmailAddress emailAddress; 9 | private List
addresses; 10 | 11 | public String getFirstName() { 12 | return firstName; 13 | } 14 | 15 | public void setFirstName(String firstName) { 16 | this.firstName = firstName; 17 | } 18 | 19 | public String getLastName() { 20 | return lastName; 21 | } 22 | 23 | public void setLastName(String lastName) { 24 | this.lastName = lastName; 25 | } 26 | 27 | public EmailAddress getEmailAddress() { 28 | return emailAddress; 29 | } 30 | 31 | public void setEmailAddress(EmailAddress emailAddress) { 32 | this.emailAddress = emailAddress; 33 | } 34 | 35 | public void setAddresses(List
addresses) { 36 | this.addresses = addresses; 37 | } 38 | 39 | public List
getAddresses() { 40 | return addresses; 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return "Customer: [" + getId() + "] " + firstName + " " + lastName + " " + emailAddress + " " + addresses; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/me/jclagache/data/mybatis/domain/EmailAddress.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.domain; 2 | 3 | public class EmailAddress { 4 | 5 | private String value; 6 | 7 | protected EmailAddress() { 8 | } 9 | 10 | public EmailAddress(String emailAddress) { 11 | this.value = emailAddress; 12 | } 13 | 14 | @Override 15 | public boolean equals(Object o) { 16 | if (this == o) return true; 17 | if (o == null || getClass() != o.getClass()) return false; 18 | EmailAddress that = (EmailAddress) o; 19 | return !(value != null ? !value.equals(that.value) : that.value != null); 20 | 21 | } 22 | 23 | @Override 24 | public int hashCode() { 25 | return value != null ? value.hashCode() : 0; 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | return value; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/me/jclagache/data/mybatis/repository/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository; 2 | 3 | import me.jclagache.data.mybatis.domain.Customer; 4 | import org.apache.ibatis.annotations.ResultMap; 5 | import org.apache.ibatis.annotations.Select; 6 | import org.springframework.data.repository.query.Param; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Mapping and queries defined in mapper/Customer/Customer.xml 12 | * except me.jclagache.data.mybatis.repository.CustomerRepository#findByLastName(java.lang.String) query 13 | */ 14 | public interface CustomerRepository extends MyBatisRepository { 15 | 16 | List findByFirstName(String firstName); 17 | 18 | /** 19 | * 20 | * org.apache.ibatis.annotations.Select query annotation can be used 21 | */ 22 | @Select("SELECT customer.id id, customer.first_name first_name, customer.last_name last_name, customer.email_address email_adress, address.id address_id, " + 23 | "address.street address_street, address.city address_city, address.country address_country FROM customer LEFT JOIN address ON customer.id = address.customer_id WHERE " + 24 | " customer.last_name = #{lastName}") 25 | @ResultMap("customerResultMap") 26 | List findByLastName(@Param("lastName")String lastName); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/me/jclagache/data/mybatis/repository/CustomerRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.repository; 2 | 3 | import me.jclagache.data.mybatis.ApplicationConfig; 4 | import me.jclagache.data.mybatis.domain.Customer; 5 | import org.hamcrest.beans.HasPropertyWithValue; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | import java.util.List; 14 | 15 | import static org.hamcrest.CoreMatchers.everyItem; 16 | import static org.hamcrest.CoreMatchers.is; 17 | import static org.junit.Assert.*; 18 | 19 | @SpringBootTest(classes = ApplicationConfig.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) 20 | @RunWith(SpringRunner.class) 21 | @Transactional 22 | public class CustomerRepositoryTest { 23 | 24 | @Autowired CustomerRepository customerRepository; 25 | 26 | @Test 27 | public void testFindOneCustomer() { 28 | Customer customer = customerRepository.findOne(100); 29 | assertNotNull(customer); 30 | assertEquals("Hi John !", "John", customer.getFirstName()); 31 | } 32 | 33 | @Test 34 | public void testFindAllCustomers() { 35 | List customers = customerRepository.findAll(); 36 | assertNotNull(customers); 37 | assertTrue(customers.size() > 0); 38 | } 39 | 40 | @Test 41 | public void testFindCustomersByFirstName() { 42 | List customers = customerRepository.findByFirstName("John"); 43 | assertNotNull(customers); 44 | assertTrue(customers.size() == 1); 45 | } 46 | 47 | @Test 48 | public void testFindCustomersByLastName() { 49 | List customers = customerRepository.findByLastName("Doe"); 50 | assertNotNull(customers); 51 | assertTrue(customers.size() == 3); 52 | assertThat(customers, everyItem(HasPropertyWithValue.hasProperty("lastName",is("Doe")))); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/me/jclagache/data/mybatis/rest/CustomerRestTest.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.rest; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | import org.springframework.test.web.servlet.MockMvc; 10 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 11 | import org.springframework.web.context.WebApplicationContext; 12 | 13 | import static org.hamcrest.Matchers.containsString; 14 | import static org.hamcrest.Matchers.hasSize; 15 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 16 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 17 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 18 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 19 | 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest(classes = RestApplicationConfig.class) 22 | public class CustomerRestTest { 23 | 24 | @Autowired 25 | private WebApplicationContext context; 26 | 27 | private MockMvc mvc; 28 | 29 | @Before 30 | public void setUp() { 31 | this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); 32 | } 33 | 34 | @Test 35 | public void testHome() throws Exception { 36 | this.mvc.perform(get("/")).andExpect(status().isOk()).andExpect(content().string(containsString("customers"))); 37 | } 38 | 39 | @Test 40 | public void testFindAllCustomers() throws Exception { 41 | this.mvc.perform(get("/customers")).andExpect(status().isOk()).andExpect(jsonPath("$._embedded.customers", hasSize(3))); 42 | } 43 | 44 | @Test 45 | public void testFindCustomersByLastName() throws Exception { 46 | this.mvc.perform(get("/customers/search/findByLastName?lastName=Doe")).andExpect(status().isOk()).andExpect(jsonPath("$._embedded.customers", hasSize(3))); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/me/jclagache/data/mybatis/rest/RestApplicationConfig.java: -------------------------------------------------------------------------------- 1 | package me.jclagache.data.mybatis.rest; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.fasterxml.jackson.annotation.PropertyAccessor; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import me.jclagache.data.mybatis.ApplicationConfig; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; 10 | 11 | @Configuration 12 | public class RestApplicationConfig extends ApplicationConfig{ 13 | 14 | @Bean 15 | public Jackson2ObjectMapperBuilder objectMapperBuilder() { 16 | return new Jackson2ObjectMapperBuilder() { 17 | 18 | @Override 19 | public void configure(ObjectMapper objectMapper) { 20 | super.configure(objectMapper); 21 | //Dont have to provide accessors 22 | objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); 23 | } 24 | 25 | }; 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/resources/config/application.properties: -------------------------------------------------------------------------------- 1 | mybatis.mapper.base.package=me.jclagache.data.mybatis.repository 2 | mybatis.aliases.package=me.jclagache.data.mybatis.domain -------------------------------------------------------------------------------- /src/test/resources/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO customer(id, first_name, last_name, email_address) VALUES(100, 'John', 'Doe', 'john@doe.com'); 2 | INSERT INTO customer(id, first_name, last_name, email_address) VALUES(101, 'Jane', 'Doe', 'jane@doe.com'); 3 | INSERT INTO customer(id, first_name, last_name, email_address) VALUES(102, 'Bob', 'Doe', 'bob@doe.com'); 4 | ALTER TABLE customer ALTER COLUMN id RESTART WITH 200; 5 | INSERT INTO address(customer_id, street, city, country) VALUES(100, '6 Main St', 'Newtown', 'USA'); 6 | INSERT INTO address(customer_id, street, city, country) VALUES(100, '128 N. South St', 'Middletown', 'USA'); 7 | INSERT INTO address(customer_id, street, city, country) VALUES(102, '512 North St', 'London', 'UK'); -------------------------------------------------------------------------------- /src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/test/resources/mapper/Customer/Customer.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 35 | 36 | 49 | 50 | -------------------------------------------------------------------------------- /src/test/resources/schema.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE address IF EXISTS; 2 | DROP TABLE customer IF EXISTS; 3 | CREATE MEMORY TABLE customer ( 4 | id BIGINT IDENTITY PRIMARY KEY, 5 | first_name VARCHAR(255), 6 | last_name VARCHAR(255), 7 | email_address VARCHAR(255)); 8 | CREATE UNIQUE INDEX ix_customer_email ON CUSTOMER (email_address ASC); 9 | CREATE MEMORY TABLE address ( 10 | id BIGINT IDENTITY PRIMARY KEY, 11 | customer_id BIGINT CONSTRAINT address_customer_ref REFERENCES customer (id), 12 | street VARCHAR(255), 13 | city VARCHAR(255), 14 | country VARCHAR(255)); --------------------------------------------------------------------------------