Browse Source

DATAJDBC-178 - Allow customizing the MyBatis namespace.

The MyBatis namespace used can now be customised by providing a NamespaceStrategy to the MyBatisDataAccessStrategy.

Original pull request: #44.
pull/56/head
Kazuki Shimizu 8 years ago committed by Jens Schauder
parent
commit
45a6d3467a
  1. 41
      src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java
  2. 36
      src/main/java/org/springframework/data/jdbc/mybatis/MyBatisNamingStrategy.java
  3. 121
      src/test/java/org/springframework/data/jdbc/mybatis/MyBatisCustomizingNamespaceHsqlIntegrationTests.java
  4. 1
      src/test/resources/org.springframework.data.jdbc.mybatis/MyBatisCustomizingNamespaceHsqlIntegrationTests-hsql.sql
  5. 22
      src/test/resources/org/springframework/data/jdbc/mybatis/mapper/DummyEntityMapper.xml

41
src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java

@ -33,7 +33,7 @@ import org.springframework.data.mapping.PropertyPath; @@ -33,7 +33,7 @@ import org.springframework.data.mapping.PropertyPath;
/**
* {@link DataAccessStrategy} implementation based on MyBatis. Each method gets mapped to a statement. The name of the
* statement gets constructed as follows: The namespace is based on the class of the entity plus the suffix "Mapper".
* statement gets constructed as follows: By default, the namespace is based on the class of the entity plus the suffix "Mapper".
* This is then followed by the method name separated by a dot. For methods taking a {@link PropertyPath} as argument,
* the relevant entity is that of the root of the path, and the path itself gets as dot separated String appended to the
* statement name. Each statement gets an instance of {@link MyBatisContext}, which at least has the entityType set. For
@ -44,9 +44,8 @@ import org.springframework.data.mapping.PropertyPath; @@ -44,9 +44,8 @@ import org.springframework.data.mapping.PropertyPath;
*/
public class MyBatisDataAccessStrategy implements DataAccessStrategy {
private static final String MAPPER_SUFFIX = "Mapper";
private final SqlSession sqlSession;
private MyBatisNamingStrategy namingStrategy = new MyBatisNamingStrategy() {};
/**
* Create a {@link DataAccessStrategy} that first checks for queries defined by MyBatis and if it doesn't find one
@ -92,30 +91,38 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy { @@ -92,30 +91,38 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
this.sqlSession = sqlSession;
}
/**
* Set a naming strategy for MyBatis objects.
* @param namingStrategy Must be non {@literal null}
*/
public void setNamingStrategy(MyBatisNamingStrategy namingStrategy) {
this.namingStrategy = namingStrategy;
}
@Override
public <T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
sqlSession().insert(mapper(domainType) + ".insert",
sqlSession().insert(namespace(domainType) + ".insert",
new MyBatisContext(null, instance, domainType, additionalParameters));
}
@Override
public <S> void update(S instance, Class<S> domainType) {
sqlSession().update(mapper(domainType) + ".update",
sqlSession().update(namespace(domainType) + ".update",
new MyBatisContext(null, instance, domainType, Collections.emptyMap()));
}
@Override
public void delete(Object id, Class<?> domainType) {
sqlSession().delete(mapper(domainType) + ".delete",
sqlSession().delete(namespace(domainType) + ".delete",
new MyBatisContext(id, null, domainType, Collections.emptyMap()));
}
@Override
public void delete(Object rootId, PropertyPath propertyPath) {
sqlSession().delete(mapper(propertyPath.getOwningType().getType()) + ".delete-" + toDashPath(propertyPath),
sqlSession().delete(namespace(propertyPath.getOwningType().getType()) + ".delete-" + toDashPath(propertyPath),
new MyBatisContext(rootId, null, propertyPath.getLeafProperty().getTypeInformation().getType(),
Collections.emptyMap()));
}
@ -124,7 +131,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy { @@ -124,7 +131,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
public <T> void deleteAll(Class<T> domainType) {
sqlSession().delete( //
mapper(domainType) + ".deleteAll", //
namespace(domainType) + ".deleteAll", //
new MyBatisContext(null, null, domainType, Collections.emptyMap()) //
);
}
@ -136,49 +143,49 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy { @@ -136,49 +143,49 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
Class leaveType = propertyPath.getLeafProperty().getTypeInformation().getType();
sqlSession().delete( //
mapper(baseType) + ".deleteAll-" + toDashPath(propertyPath), //
namespace(baseType) + ".deleteAll-" + toDashPath(propertyPath), //
new MyBatisContext(null, null, leaveType, Collections.emptyMap()) //
);
}
@Override
public <T> T findById(Object id, Class<T> domainType) {
return sqlSession().selectOne(mapper(domainType) + ".findById",
return sqlSession().selectOne(namespace(domainType) + ".findById",
new MyBatisContext(id, null, domainType, Collections.emptyMap()));
}
@Override
public <T> Iterable<T> findAll(Class<T> domainType) {
return sqlSession().selectList(mapper(domainType) + ".findAll",
return sqlSession().selectList(namespace(domainType) + ".findAll",
new MyBatisContext(null, null, domainType, Collections.emptyMap()));
}
@Override
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
return sqlSession().selectList(mapper(domainType) + ".findAllById",
return sqlSession().selectList(namespace(domainType) + ".findAllById",
new MyBatisContext(ids, null, domainType, Collections.emptyMap()));
}
@Override
public <T> Iterable<T> findAllByProperty(Object rootId, JdbcPersistentProperty property) {
return sqlSession().selectList(mapper(property.getOwner().getType()) + ".findAllByProperty-" + property.getName(),
return sqlSession().selectList(namespace(property.getOwner().getType()) + ".findAllByProperty-" + property.getName(),
new MyBatisContext(rootId, null, property.getType(), Collections.emptyMap()));
}
@Override
public <T> boolean existsById(Object id, Class<T> domainType) {
return sqlSession().selectOne(mapper(domainType) + ".existsById",
return sqlSession().selectOne(namespace(domainType) + ".existsById",
new MyBatisContext(id, null, domainType, Collections.emptyMap()));
}
@Override
public long count(Class<?> domainType) {
return sqlSession().selectOne(mapper(domainType) + ".count",
return sqlSession().selectOne(namespace(domainType) + ".count",
new MyBatisContext(null, null, domainType, Collections.emptyMap()));
}
private String mapper(Class<?> domainType) {
return domainType.getName() + MAPPER_SUFFIX;
private String namespace(Class<?> domainType) {
return this.namingStrategy.getNamespace(domainType);
}
private SqlSession sqlSession() {

36
src/main/java/org/springframework/data/jdbc/mybatis/MyBatisNamingStrategy.java

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.mybatis;
/**
* The naming strategy for MyBatis.
*
* @author Kazuki Shimizu
*/
public interface MyBatisNamingStrategy {
/**
* Get a namespace that correspond domain type.
* <p>
* By default, the namespace is based on the class of the entity plus the suffix "Mapper".
* @param domainType Must be non {@literal null}.
* @return a namespace that correspond domain type
*/
default String getNamespace(Class<?> domainType) {
return domainType.getName() + "Mapper";
}
}

121
src/test/java/org/springframework/data/jdbc/mybatis/MyBatisCustomizingNamespaceHsqlIntegrationTests.java

@ -0,0 +1,121 @@ @@ -0,0 +1,121 @@
/*
* Copyright 2017-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.mybatis;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException;
import junit.framework.AssertionFailedError;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.repository.CrudRepository;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
import org.springframework.transaction.annotation.Transactional;
/**
* Tests the integration for customizing namespace with Mybatis.
*
* @author Kazuki Shimizu
*/
@ContextConfiguration
@Transactional
public class MyBatisCustomizingNamespaceHsqlIntegrationTests {
@org.springframework.context.annotation.Configuration
@Import(TestConfiguration.class)
@EnableJdbcRepositories(considerNestedRepositories = true)
static class Config {
@Bean
Class<?> testClass() {
return MyBatisCustomizingNamespaceHsqlIntegrationTests.class;
}
@Bean
SqlSessionFactoryBean createSessionFactory(EmbeddedDatabase db) throws IOException {
Configuration configuration = new Configuration();
configuration.getTypeAliasRegistry().registerAlias("MyBatisContext", MyBatisContext.class);
configuration.getTypeAliasRegistry().registerAlias(DummyEntity.class);
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(db);
sqlSessionFactoryBean.setConfiguration(configuration);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:org/springframework/data/jdbc/mybatis/mapper/*Mapper.xml"));
return sqlSessionFactoryBean;
}
@Bean
SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory factory) {
return new SqlSessionTemplate(factory);
}
@Bean
MyBatisDataAccessStrategy dataAccessStrategy(SqlSession sqlSession) {
MyBatisDataAccessStrategy strategy = new MyBatisDataAccessStrategy(sqlSession);
strategy.setNamingStrategy(new MyBatisNamingStrategy() {
@Override
public String getNamespace(Class<?> domainType) {
return domainType.getPackage().getName() + ".mapper." + domainType.getSimpleName() + "Mapper";
}
});
return strategy;
}
}
@ClassRule public static final SpringClassRule classRule = new SpringClassRule();
@Rule public SpringMethodRule methodRule = new SpringMethodRule();
@Autowired SqlSessionFactory sqlSessionFactory;
@Autowired DummyEntityRepository repository;
@Test // DATAJDBC-178
public void myBatisGetsUsedForInsertAndSelect() {
DummyEntity entity = new DummyEntity(null, "some name");
DummyEntity saved = repository.save(entity);
assertThat(saved.id).isNotNull();
DummyEntity reloaded = repository.findById(saved.id).orElseThrow(AssertionFailedError::new);
assertThat(reloaded.name).isEqualTo("name " + saved.id);
}
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {
}
}

1
src/test/resources/org.springframework.data.jdbc.mybatis/MyBatisCustomizingNamespaceHsqlIntegrationTests-hsql.sql

@ -0,0 +1 @@ @@ -0,0 +1 @@
CREATE TABLE dummyentity(id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY);

22
src/test/resources/org/springframework/data/jdbc/mybatis/mapper/DummyEntityMapper.xml

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
<?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="org.springframework.data.jdbc.mybatis.mapper.DummyEntityMapper">
<resultMap id="dummyEntityMap" type="DummyEntity">
<constructor>
<idArg column="id" javaType="long"/>
<arg column="name" javaType="String"/>
</constructor>
</resultMap>
<insert id="insert" parameterType="MyBatisContext" useGeneratedKeys="true" keyProperty="instance.id" keyColumn="ID">
INSERT INTO DummyEntity (id) VALUES (DEFAULT)
</insert>
<select id="findById" resultType="MyBatisContext" resultMap="dummyEntityMap">
SELECT
id,
'name ' || id as name
FROM DummyEntity
WHERE id = #{id}
</select>
</mapper>
Loading…
Cancel
Save