#314 - Added MyBatis example.

MyBatisTests demonstrates how some queries executed by Spring Data JDBC can be replaced with MyBatis mappings.
The domain model is based on the basic JDBC.

The map of models is maintained by two statements configured in MyBatis mappings.

example.springdata.jdbc.mybatis.LegoSetMapper.findAllByProperty-models showcases how a map can be loaded by configuring the select to return instances of Map.Entry

example.springdata.jdbc.mybatis.Model.insert showcases how one can access the MyBatixContext and thereby the instance to save and the key of the parent entity.

Original pull request: #345.
This commit is contained in:
Jens Schauder
2017-12-01 10:56:40 +01:00
committed by Oliver Gierke
parent 8884d98971
commit 047610df5f
17 changed files with 535 additions and 0 deletions

10
jdbc/mybatis/README.adoc Normal file
View File

@@ -0,0 +1,10 @@
== Spring Data JDBC mybatis
`MyBatisTests` demonstrates how some queries executed by Spring Data JDBC can be replaced with MyBatis mappings.
The domain model is based on the basic JDBC example for comparison.
The map of models is maintained by two statements configured in MyBatis mappings.
`example.springdata.jdbc.mybatis.LegoSetMapper.findAllByProperty-models` showcases how a map can be loaded by configuring the select to return instances of `Map.Entry`
`example.springdata.jdbc.mybatis.Model.insert` showcases how one can access the `MyBatixContext` and thereby the instance to save and the key of the parent entity.

33
jdbc/mybatis/pom.xml Normal file
View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-jdbc-mybatis</artifactId>
<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-jdbc-examples</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Spring Data JDBC - Using MyBatis for defining queries</name>
<description>Sample project demonstrating Spring Data JDBC features</description>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>1.3.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,26 @@
/*
* 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 example.springdata.jdbc.mybatis;
/**
* Age group for which a {@link LegoSet} is intended.
*
* @author Jens Schauder
*/
public enum AgeGroup {
_0to3, _3to8, _8to12, _12andOlder
}

View File

@@ -0,0 +1,56 @@
/*
* 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 example.springdata.jdbc.mybatis;
import lombok.Data;
import java.time.Period;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
import org.springframework.data.annotation.Id;
/**
* A Lego Set consisting of multiple Blocks and a manual
*
* @author Jens Schauder
*/
@Data
public class LegoSet {
// You can build multiple models from one LegoSet
private final Map<String, Model> models = new HashMap<>();
@Id private Integer id;
private String name;
private Manual manual;
private static int toInt(Period period) {
return (int) (period == null ? 0 : period.get(ChronoUnit.YEARS));
}
private static Period toPeriod(int years) {
return Period.ofYears(years);
}
public void addModel(String name, String description) {
Model model = new Model();
model.name = name;
model.description = description;
models.put(name, model);
}
}

View File

@@ -0,0 +1,27 @@
/*
* 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 example.springdata.jdbc.mybatis;
import org.springframework.data.repository.CrudRepository;
/**
* A repository for {@link LegoSet}.
*
* @author Jens Schauder
*/
public interface LegoSetRepository extends CrudRepository<LegoSet, Integer> {
}

View File

@@ -0,0 +1,40 @@
/*
* 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 example.springdata.jdbc.mybatis;
import lombok.Data;
import org.springframework.data.annotation.Id;
/**
* A manual instructing how to assemble a {@link LegoSet}.
*
* @author Jens Schauder
*/
@Data
public class Manual {
@Id private Long id;
private String author;
private String text;
Manual(String text, String author) {
this.id = null;
this.author = author;
this.text = text;
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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 example.springdata.jdbc.mybatis;
import lombok.ToString;
import org.springframework.data.domain.Persistable;
import org.springframework.lang.Nullable;
/**
* One of potentially multiple models that can be build from a single {@link LegoSet}.
* <p>
* No getters or setters needed.
*
* @author Jens Schauder
*/
@ToString
public class Model implements Persistable<String> {
String name;
String description;
@Nullable
@Override
public String getId() {
return name;
}
@Override
public boolean isNew() {
return true;
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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 example.springdata.jdbc.mybatis;
import java.sql.Clob;
import java.sql.SQLException;
import java.util.Map;
/**
* @author Jens Schauder
*/
public class ModelMapEntry implements Map.Entry<String, Model> {
private final String key;
private final Model value;
ModelMapEntry(String name, Clob description) {
key = name;
value = new Model();
value.name = name;
try {
value.description = description.getSubString(1, (int) description.length());
} catch (SQLException se) {
throw new RuntimeException(se);
}
}
@Override
public String getKey() {
return key;
}
@Override
public Model getValue() {
return value;
}
@Override
public Model setValue(Model value) {
throw new UnsupportedOperationException("can't set the value of a ModelMapEntry");
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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 example.springdata.jdbc.mybatis;
import static java.util.Arrays.*;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSession;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jdbc.core.CascadingDataAccessStrategy;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
import org.springframework.data.jdbc.core.DelegatingDataAccessStrategy;
import org.springframework.data.jdbc.core.SqlGeneratorSource;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mybatis.MyBatisDataAccessStrategy;
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
/**
* @author Jens Schauder
*/
@Configuration
@EnableJdbcRepositories
public class MyBatisConfiguration {
// temporary workaround for https://jira.spring.io/browse/DATAJDBC-155
@Bean
DataAccessStrategy defaultDataAccessStrategy(JdbcMappingContext context, DataSource dataSource,
SqlSession sqlSession) {
NamedParameterJdbcOperations operations = new NamedParameterJdbcTemplate(dataSource);
DelegatingDataAccessStrategy delegatingDataAccessStrategy = new DelegatingDataAccessStrategy();
MyBatisDataAccessStrategy myBatisDataAccessStrategy = new MyBatisDataAccessStrategy(sqlSession);
CascadingDataAccessStrategy cascadingDataAccessStrategy = new CascadingDataAccessStrategy(
asList(myBatisDataAccessStrategy, delegatingDataAccessStrategy));
DefaultDataAccessStrategy defaultDataAccessStrategy = new DefaultDataAccessStrategy( //
new SqlGeneratorSource(context), //
operations, //
context, //
cascadingDataAccessStrategy);
delegatingDataAccessStrategy.setDelegate(defaultDataAccessStrategy);
return cascadingDataAccessStrategy;
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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 example.springdata.jdbc.mybatis;
import lombok.experimental.UtilityClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Trivial class to print domain objects to the console in a somewhat readable format.
*
* @author Jens Schauder
*/
@UtilityClass
public class Output {
private final Logger LOG = LoggerFactory.getLogger(Output.class);
public static void list(Iterable<?> categories, String title) {
StringBuilder message = new StringBuilder(String.format("==== %s ====\n", title));
categories.forEach(category -> {
message.append(category.toString().replace(", ", ",\n\t"));
});
LOG.info(message.toString());
}
}

View File

@@ -0,0 +1,4 @@
logging.level.org.springframework.data=INFO
logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG
logging.level.example.springdata.jdbc.mybatis=TRACE
mybatis.config-location=mybatis-config.xml

View File

@@ -0,0 +1,14 @@
<?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="example.springdata.jdbc.mybatis.LegoSetMapper">
<select id="findAllByProperty-models" resultType="ModelMapEntry"
parameterType="org.springframework.data.jdbc.mybatis.MyBatisContext">
SELECT
name,
description
FROM Model
WHERE legoset = #{id}
</select>
</mapper>

View File

@@ -0,0 +1,11 @@
<?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="example.springdata.jdbc.mybatis.ModelMapper">
<insert id="insert" parameterType="org.springframework.data.jdbc.mybatis.MyBatisContext">
<bind name="parentId" value="_parameter.get('LegoSet')"/>
INSERT INTO Model (name, description, legoset) VALUES (#{instance.name}, #{instance.description}, #{parentId})
</insert>
</mapper>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="example.springdata.jdbc.mybatis"/>
</typeAliases>
<mappers>
<mapper resource="example/springdata/jdbc/mybatis/Model.xml"/>
<mapper resource="example/springdata/jdbc/mybatis/LegoSet.xml"/>
</mappers>
</configuration>

View File

@@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS LegoSet (
id INTEGER IDENTITY PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE IF NOT EXISTS manual (
id INTEGER IDENTITY PRIMARY KEY,
LegoSet INTEGER,
author CHAR(100),
text VARCHAR(1000)
);
CREATE TABLE IF NOT EXISTS Model (
name VARCHAR(100),
description CLOB,
legoset INTEGER
);

View File

@@ -0,0 +1,73 @@
package example.springdata.jdbc.mybatis;
/*
* 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.
*/
import static org.assertj.core.api.Assertions.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureJdbc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Demonstrates queries can be mapped using MyBatis.
*
* @author Jens Schauder
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MyBatisConfiguration.class)
@AutoConfigureJdbc
@MybatisTest
public class MyBatisTests {
@Autowired private LegoSetRepository repository;
@Test
public void exerciseSomewhatComplexEntity() {
LegoSet smallCar = createLegoSet();
smallCar.setManual(new Manual("Just put all the pieces together in the right order", "Jens Schauder"));
smallCar.addModel("suv", "SUV with sliding doors.");
smallCar.addModel("roadster", "Slick red roadster.");
repository.save(smallCar);
assertThat(smallCar.getId()).isNotNull();
assertThat(repository.findById(smallCar.getId()).get().getModels()).hasSize(2);
Output.list(repository.findAll(), "Original LegoSet");
smallCar.getManual().setText("Just make it so it looks like a car.");
smallCar.addModel("pickup", "A pickup truck with some tools in the back.");
repository.save(smallCar);
Output.list(repository.findAll(), "Updated");
smallCar.setManual(new Manual("One last attempt: Just build a car! Ok?", "Jens Schauder"));
repository.save(smallCar);
Output.list(repository.findAll(), "Manual replaced");
}
private LegoSet createLegoSet() {
LegoSet smallCar = new LegoSet();
smallCar.setName("Small Car 01");
return smallCar;
}
}

View File

@@ -18,6 +18,7 @@
<modules>
<module>basics</module>
<module>mybatis</module>
</modules>
<properties>