Skip to content

DATAJDBC-178 - Allow generating any namespace in MyBatisDataAccessStrategy #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

/**
* {@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
Expand All @@ -37,9 +37,8 @@
*/
public class MyBatisDataAccessStrategy implements DataAccessStrategy {

private static final String MAPPER_SUFFIX = "Mapper";

private final SqlSession sqlSession;
private MyBatisNamingStrategy namingStrategy = new MyBatisNamingStrategy() {};

/**
* Constructs a {@link DataAccessStrategy} based on MyBatis.
Expand All @@ -53,30 +52,38 @@ public MyBatisDataAccessStrategy(SqlSession sqlSession) {
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()));
}
Expand All @@ -85,7 +92,7 @@ public void delete(Object rootId, PropertyPath propertyPath) {
public <T> void deleteAll(Class<T> domainType) {

sqlSession().delete( //
mapper(domainType) + ".deleteAll", //
namespace(domainType) + ".deleteAll", //
new MyBatisContext(null, null, domainType, Collections.emptyMap()) //
);
}
Expand All @@ -97,49 +104,49 @@ public <T> void deleteAll(PropertyPath propertyPath) {
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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
}

}
Original file line number Diff line number Diff line change
@@ -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> {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE TABLE dummyentity(id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY);
Original file line number Diff line number Diff line change
@@ -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>