From 047610df5f9407c3fefd5b7cf9b188a474fb8a97 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 1 Dec 2017 10:56:40 +0100 Subject: [PATCH] #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. --- jdbc/mybatis/README.adoc | 10 +++ jdbc/mybatis/pom.xml | 33 +++++++++ .../springdata/jdbc/mybatis/AgeGroup.java | 26 +++++++ .../springdata/jdbc/mybatis/LegoSet.java | 56 ++++++++++++++ .../jdbc/mybatis/LegoSetRepository.java | 27 +++++++ .../springdata/jdbc/mybatis/Manual.java | 40 ++++++++++ .../springdata/jdbc/mybatis/Model.java | 47 ++++++++++++ .../jdbc/mybatis/ModelMapEntry.java | 56 ++++++++++++++ .../jdbc/mybatis/MyBatisConfiguration.java | 66 +++++++++++++++++ .../springdata/jdbc/mybatis/Output.java | 43 +++++++++++ .../src/main/resources/application.properties | 4 + .../springdata/jdbc/mybatis/LegoSet.xml | 14 ++++ .../example/springdata/jdbc/mybatis/Model.xml | 11 +++ .../src/main/resources/mybatis-config.xml | 13 ++++ jdbc/mybatis/src/main/resources/schema.sql | 15 ++++ .../springdata/jdbc/mybatis/MyBatisTests.java | 73 +++++++++++++++++++ jdbc/pom.xml | 1 + 17 files changed, 535 insertions(+) create mode 100644 jdbc/mybatis/README.adoc create mode 100644 jdbc/mybatis/pom.xml create mode 100644 jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/AgeGroup.java create mode 100644 jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/LegoSet.java create mode 100644 jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/LegoSetRepository.java create mode 100644 jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/Manual.java create mode 100644 jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/Model.java create mode 100644 jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/ModelMapEntry.java create mode 100644 jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/MyBatisConfiguration.java create mode 100644 jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/Output.java create mode 100644 jdbc/mybatis/src/main/resources/application.properties create mode 100644 jdbc/mybatis/src/main/resources/example/springdata/jdbc/mybatis/LegoSet.xml create mode 100644 jdbc/mybatis/src/main/resources/example/springdata/jdbc/mybatis/Model.xml create mode 100644 jdbc/mybatis/src/main/resources/mybatis-config.xml create mode 100644 jdbc/mybatis/src/main/resources/schema.sql create mode 100644 jdbc/mybatis/src/test/java/example/springdata/jdbc/mybatis/MyBatisTests.java diff --git a/jdbc/mybatis/README.adoc b/jdbc/mybatis/README.adoc new file mode 100644 index 00000000..131224d1 --- /dev/null +++ b/jdbc/mybatis/README.adoc @@ -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. diff --git a/jdbc/mybatis/pom.xml b/jdbc/mybatis/pom.xml new file mode 100644 index 00000000..0ff45643 --- /dev/null +++ b/jdbc/mybatis/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + spring-data-jdbc-mybatis + + + org.springframework.data.examples + spring-data-jdbc-examples + 2.0.0.BUILD-SNAPSHOT + ../pom.xml + + + Spring Data JDBC - Using MyBatis for defining queries + Sample project demonstrating Spring Data JDBC features + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 1.3.1 + + + org.mybatis.spring.boot + mybatis-spring-boot-starter-test + 1.3.1 + test + + + + \ No newline at end of file diff --git a/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/AgeGroup.java b/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/AgeGroup.java new file mode 100644 index 00000000..a1e2d889 --- /dev/null +++ b/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/AgeGroup.java @@ -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 +} diff --git a/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/LegoSet.java b/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/LegoSet.java new file mode 100644 index 00000000..6128e046 --- /dev/null +++ b/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/LegoSet.java @@ -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 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); + } +} diff --git a/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/LegoSetRepository.java b/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/LegoSetRepository.java new file mode 100644 index 00000000..356ca1bf --- /dev/null +++ b/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/LegoSetRepository.java @@ -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 { + +} diff --git a/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/Manual.java b/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/Manual.java new file mode 100644 index 00000000..dee2fd32 --- /dev/null +++ b/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/Manual.java @@ -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; + } +} diff --git a/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/Model.java b/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/Model.java new file mode 100644 index 00000000..b7e8f409 --- /dev/null +++ b/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/Model.java @@ -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}. + *

+ * No getters or setters needed. + * + * @author Jens Schauder + */ +@ToString +public class Model implements Persistable { + + String name; + + String description; + + @Nullable + @Override + public String getId() { + return name; + } + + @Override + public boolean isNew() { + return true; + } +} diff --git a/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/ModelMapEntry.java b/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/ModelMapEntry.java new file mode 100644 index 00000000..da61ea98 --- /dev/null +++ b/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/ModelMapEntry.java @@ -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 { + + 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"); + } +} diff --git a/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/MyBatisConfiguration.java b/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/MyBatisConfiguration.java new file mode 100644 index 00000000..6326847a --- /dev/null +++ b/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/MyBatisConfiguration.java @@ -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; + } +} diff --git a/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/Output.java b/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/Output.java new file mode 100644 index 00000000..7459f3f9 --- /dev/null +++ b/jdbc/mybatis/src/main/java/example/springdata/jdbc/mybatis/Output.java @@ -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()); + } +} diff --git a/jdbc/mybatis/src/main/resources/application.properties b/jdbc/mybatis/src/main/resources/application.properties new file mode 100644 index 00000000..1fce1e07 --- /dev/null +++ b/jdbc/mybatis/src/main/resources/application.properties @@ -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 \ No newline at end of file diff --git a/jdbc/mybatis/src/main/resources/example/springdata/jdbc/mybatis/LegoSet.xml b/jdbc/mybatis/src/main/resources/example/springdata/jdbc/mybatis/LegoSet.xml new file mode 100644 index 00000000..b174a89e --- /dev/null +++ b/jdbc/mybatis/src/main/resources/example/springdata/jdbc/mybatis/LegoSet.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/jdbc/mybatis/src/main/resources/example/springdata/jdbc/mybatis/Model.xml b/jdbc/mybatis/src/main/resources/example/springdata/jdbc/mybatis/Model.xml new file mode 100644 index 00000000..66cc3c4b --- /dev/null +++ b/jdbc/mybatis/src/main/resources/example/springdata/jdbc/mybatis/Model.xml @@ -0,0 +1,11 @@ + + + + + + + INSERT INTO Model (name, description, legoset) VALUES (#{instance.name}, #{instance.description}, #{parentId}) + + \ No newline at end of file diff --git a/jdbc/mybatis/src/main/resources/mybatis-config.xml b/jdbc/mybatis/src/main/resources/mybatis-config.xml new file mode 100644 index 00000000..ec0c130f --- /dev/null +++ b/jdbc/mybatis/src/main/resources/mybatis-config.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/jdbc/mybatis/src/main/resources/schema.sql b/jdbc/mybatis/src/main/resources/schema.sql new file mode 100644 index 00000000..586ef36c --- /dev/null +++ b/jdbc/mybatis/src/main/resources/schema.sql @@ -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 +); diff --git a/jdbc/mybatis/src/test/java/example/springdata/jdbc/mybatis/MyBatisTests.java b/jdbc/mybatis/src/test/java/example/springdata/jdbc/mybatis/MyBatisTests.java new file mode 100644 index 00000000..f3f402ec --- /dev/null +++ b/jdbc/mybatis/src/test/java/example/springdata/jdbc/mybatis/MyBatisTests.java @@ -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; + } +} diff --git a/jdbc/pom.xml b/jdbc/pom.xml index fc59c06f..0b50b4b9 100644 --- a/jdbc/pom.xml +++ b/jdbc/pom.xml @@ -18,6 +18,7 @@ basics + mybatis