diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateVersion.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateVersion.java new file mode 100644 index 0000000000..25b6d6534a --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateVersion.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2016 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.boot.autoconfigure.orm.jpa; + +import org.springframework.util.ClassUtils; + +/** + * Supported Hibernate versions. + * + * @author Phillip Webb + */ +enum HibernateVersion { + + /** + * Version 4. + */ + V4, + + /** + * Version 5. + */ + V5; + + private static final String HIBERNATE_5_CLASS = "org.hibernate.boot.model." + + "naming.PhysicalNamingStrategy"; + + private static HibernateVersion running; + + public static HibernateVersion getRunning() { + if (running == null) { + setRunning(ClassUtils.isPresent(HIBERNATE_5_CLASS, null) ? V5 : V4); + } + return running; + } + + static void setRunning(HibernateVersion running) { + HibernateVersion.running = running; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java index 8babe4a5b8..35c2a79c2a 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -23,6 +23,7 @@ import javax.sql.DataSource; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDatabaseConnection; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.orm.jpa.vendor.Database; import org.springframework.util.StringUtils; @@ -31,6 +32,7 @@ import org.springframework.util.StringUtils; * * @author Dave Syer * @author Andy Wilkinson + * @author Stephane Nicoll * @since 1.1.0 */ @ConfigurationProperties(prefix = "spring.jpa") @@ -125,12 +127,8 @@ public class JpaProperties { public static class Hibernate { - private static final String DEFAULT_NAMING_STRATEGY = "org.springframework.boot.orm.jpa.hibernate.SpringNamingStrategy"; - - /** - * Naming strategy fully qualified name. - */ - private Class namingStrategy; + private static final String USE_NEW_ID_GENERATOR_MAPPINGS = "hibernate.id." + + "new_generator_mappings"; /** * DDL mode. This is actually a shortcut for the "hibernate.hbm2ddl.auto" @@ -139,12 +137,25 @@ public class JpaProperties { */ private String ddlAuto; - public Class getNamingStrategy() { - return this.namingStrategy; + /** + * Use Hibernate's newer IdentifierGenerator for AUTO, TABLE and SEQUENCE. This is + * actually a shortcut for the "hibernate.id.new_generator_mappings" property. + * When not specified will default to "false" with Hibernate 5 for back + * compatibility. + */ + private Boolean useNewIdGeneratorMappings; + + private final Naming naming = new Naming(); + + @Deprecated + @DeprecatedConfigurationProperty(replacement = "spring.jpa.hibernate.naming.strategy") + public String getNamingStrategy() { + return getNaming().getStrategy(); } - public void setNamingStrategy(Class namingStrategy) { - this.namingStrategy = namingStrategy; + @Deprecated + public void setNamingStrategy(String namingStrategy) { + getNaming().setStrategy(namingStrategy); } public String getDdlAuto() { @@ -155,13 +166,23 @@ public class JpaProperties { this.ddlAuto = ddlAuto; } + public boolean isUseNewIdGeneratorMappings() { + return this.useNewIdGeneratorMappings; + } + + public void setUseNewIdGeneratorMappings(boolean useNewIdGeneratorMappings) { + this.useNewIdGeneratorMappings = useNewIdGeneratorMappings; + } + + public Naming getNaming() { + return this.naming; + } + private Map getAdditionalProperties(Map existing, DataSource dataSource) { Map result = new HashMap(existing); - if (!existing.containsKey("hibernate." + "ejb.naming_strategy_delegator")) { - result.put("hibernate.ejb.naming_strategy", - getHibernateNamingStrategy(existing)); - } + applyNewIdGeneratorMappings(result); + getNaming().applyNamingStrategy(result); String ddlAuto = getOrDeduceDdlAuto(existing, dataSource); if (StringUtils.hasText(ddlAuto) && !"none".equals(ddlAuto)) { result.put("hibernate.hbm2ddl.auto", ddlAuto); @@ -172,12 +193,15 @@ public class JpaProperties { return result; } - private String getHibernateNamingStrategy(Map existing) { - if (!existing.containsKey("hibernate." + "ejb.naming_strategy") - && this.namingStrategy != null) { - return this.namingStrategy.getName(); + private void applyNewIdGeneratorMappings(Map result) { + if (this.useNewIdGeneratorMappings != null) { + result.put(USE_NEW_ID_GENERATOR_MAPPINGS, + this.useNewIdGeneratorMappings.toString()); + } + else if (HibernateVersion.getRunning() == HibernateVersion.V5 + && !result.containsKey(USE_NEW_ID_GENERATOR_MAPPINGS)) { + result.put(USE_NEW_ID_GENERATOR_MAPPINGS, "false"); } - return DEFAULT_NAMING_STRATEGY; } private String getOrDeduceDdlAuto(Map existing, @@ -203,4 +227,96 @@ public class JpaProperties { } + public static class Naming { + + private static final String DEFAULT_HIBERNATE4_STRATEGY = "org.springframework.boot.orm.jpa.hibernate.SpringNamingStrategy"; + + private static final String DEFAULT_PHYSICAL_STRATEGY = "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy"; + + /** + * Hibernate 5 implicit naming strategy fully qualified name. + */ + private String implicitStrategy; + + /** + * Hibernate 5 physical naming strategy fully qualified name. + */ + private String physicalStrategy; + + /** + * Hibernate 4 naming strategy fully qualified name. Not supported with Hibernate + * 5. + */ + private String strategy; + + public String getImplicitStrategy() { + return this.implicitStrategy; + } + + public void setImplicitStrategy(String implicitStrategy) { + this.implicitStrategy = implicitStrategy; + } + + public String getPhysicalStrategy() { + return this.physicalStrategy; + } + + public void setPhysicalStrategy(String physicalStrategy) { + this.physicalStrategy = physicalStrategy; + } + + public String getStrategy() { + return this.strategy; + } + + public void setStrategy(String strategy) { + this.strategy = strategy; + } + + private void applyNamingStrategy(Map properties) { + switch (HibernateVersion.getRunning()) { + case V4: + applyHibernate4NamingStrategy(properties); + break; + case V5: + applyHibernate5NamingStrategy(properties); + break; + } + } + + private void applyHibernate5NamingStrategy(Map properties) { + applyHibernate5NamingStrategy(properties, + "hibernate.implicit_naming_strategy", this.implicitStrategy, null); + applyHibernate5NamingStrategy(properties, + "hibernate.physical_naming_strategy", this.physicalStrategy, + DEFAULT_PHYSICAL_STRATEGY); + } + + private void applyHibernate5NamingStrategy(Map properties, + String key, String strategy, String defaultStrategy) { + if (strategy != null) { + properties.put(key, strategy); + } + else if (defaultStrategy != null && !properties.containsKey(key)) { + properties.put(key, defaultStrategy); + } + } + + private void applyHibernate4NamingStrategy(Map properties) { + if (!properties.containsKey("hibernate.ejb.naming_strategy_delegator")) { + properties.put("hibernate.ejb.naming_strategy", + getHibernate4NamingStrategy(properties)); + } + } + + private String getHibernate4NamingStrategy(Map existing) { + if (!existing.containsKey("hibernate.ejb.naming_strategy") + && this.strategy != null) { + return this.strategy; + } + return DEFAULT_HIBERNATE4_STRATEGY; + } + + } + } diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index e16833216b..8b52e14b74 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -447,7 +447,29 @@ ] }, { - "name": "spring.jpa.hibernate.naming-strategy", + "name": "spring.jpa.hibernate.naming.implicit-strategy", + "providers": [ + { + "name": "class-reference", + "parameters": { + "target": "org.hibernate.boot.model.naming.ImplicitNamingStrategy" + } + } + ] + }, + { + "name": "spring.jpa.hibernate.naming.physical-strategy", + "providers": [ + { + "name": "class-reference", + "parameters": { + "target": "org.hibernate.boot.model.naming.PhysicalNamingStrategy" + } + } + ] + }, + { + "name": "spring.jpa.hibernate.naming.strategy", "providers": [ { "name": "class-reference", diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jpa/city/City.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jpa/city/City.java index 03a2523504..cc26cf7294 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jpa/city/City.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jpa/city/City.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2016 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. diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java index 5ca4e4a913..7ff22c36ec 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java @@ -26,6 +26,7 @@ import javax.transaction.TransactionManager; import javax.transaction.UserTransaction; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; +import org.junit.After; import org.junit.Test; import org.springframework.beans.factory.BeanCreationException; @@ -49,6 +50,11 @@ import static org.assertj.core.api.Assertions.assertThat; public class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTests { + @After + public void cleanup() { + HibernateVersion.setRunning(null); + } + @Override protected Class getAutoConfigureClass() { return HibernateJpaAutoConfiguration.class; @@ -80,6 +86,7 @@ public class HibernateJpaAutoConfigurationTests @Test public void testCustomNamingStrategy() throws Exception { + HibernateVersion.setRunning(HibernateVersion.V4); EnvironmentTestUtils.addEnvironment(this.context, "spring.jpa.hibernate.namingStrategy:" + "org.hibernate.cfg.EJB3NamingStrategy"); @@ -94,6 +101,7 @@ public class HibernateJpaAutoConfigurationTests @Test public void testCustomNamingStrategyViaJpaProperties() throws Exception { + HibernateVersion.setRunning(HibernateVersion.V4); EnvironmentTestUtils.addEnvironment(this.context, "spring.jpa.properties.hibernate.ejb.naming_strategy:" + "org.hibernate.cfg.EJB3NamingStrategy"); diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/JpaPropertiesTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/JpaPropertiesTests.java new file mode 100644 index 0000000000..cbe98e2830 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/JpaPropertiesTests.java @@ -0,0 +1,176 @@ +/* + * Copyright 2012-2016 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.boot.autoconfigure.orm.jpa; + +import java.sql.SQLException; +import java.util.Map; + +import javax.sql.DataSource; + +import org.hibernate.cfg.AvailableSettings; +import org.junit.After; +import org.junit.Test; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link JpaProperties}. + * + * @author Stephane Nicoll + */ +public class JpaPropertiesTests { + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + HibernateVersion.setRunning(null); + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void hibernate4CustomNamingStrategy() throws Exception { + JpaProperties properties = load(HibernateVersion.V4, + "spring.jpa.hibernate.naming.strategy:" + + "org.hibernate.cfg.EJB3NamingStrategy"); + Map hibernateProperties = properties + .getHibernateProperties(mockStandaloneDataSource()); + assertThat(hibernateProperties).contains(entry("hibernate.ejb.naming_strategy", + "org.hibernate.cfg.EJB3NamingStrategy")); + assertThat(hibernateProperties).doesNotContainKeys( + "hibernate.implicit_naming_strategy", + "hibernate.physical_naming_strategy"); + } + + @Test + public void hibernate4CustomNamingStrategyViaJpaProperties() throws Exception { + JpaProperties properties = load(HibernateVersion.V4, + "spring.jpa.properties.hibernate.ejb.naming_strategy:" + + "org.hibernate.cfg.EJB3NamingStrategy"); + Map hibernateProperties = properties + .getHibernateProperties(mockStandaloneDataSource()); + String actual = hibernateProperties.get("hibernate.ejb.naming_strategy"); + // You can't override this one from spring.jpa.properties because it has an + // opinionated default + assertThat(actual).isNotEqualTo("org.hibernate.cfg.EJB3NamingStrategy"); + } + + @Test + public void hibernate5NoCustomNamingStrategy() throws Exception { + JpaProperties properties = load(HibernateVersion.V5); + Map hibernateProperties = properties + .getHibernateProperties(mockStandaloneDataSource()); + assertThat(hibernateProperties).doesNotContainKeys( + "hibernate.ejb.naming_strategy", "hibernate.implicit_naming_strategy"); + assertThat(hibernateProperties).containsEntry( + "hibernate.physical_naming_strategy", + SpringPhysicalNamingStrategy.class.getName()); + } + + @Test + public void hibernate5CustomNamingStrategies() throws Exception { + JpaProperties properties = load(HibernateVersion.V5, + "spring.jpa.hibernate.naming.implicit-strategy:com.example.Implicit", + "spring.jpa.hibernate.naming.physical-strategy:com.example.Physical"); + Map hibernateProperties = properties + .getHibernateProperties(mockStandaloneDataSource()); + assertThat(hibernateProperties).contains( + entry("hibernate.implicit_naming_strategy", "com.example.Implicit"), + entry("hibernate.physical_naming_strategy", "com.example.Physical")); + assertThat(hibernateProperties) + .doesNotContainKeys("hibernate.ejb.naming_strategy"); + } + + @Test + public void hibernate5CustomNamingStrategiesViaJpaProperties() throws Exception { + JpaProperties properties = load(HibernateVersion.V5, + "spring.jpa.properties.hibernate.implicit_naming_strategy:com.example.Implicit", + "spring.jpa.properties.hibernate.physical_naming_strategy:com.example.Physical"); + Map hibernateProperties = properties + .getHibernateProperties(mockStandaloneDataSource()); + // You can override them as we don't provide any default + assertThat(hibernateProperties).contains( + entry("hibernate.implicit_naming_strategy", "com.example.Implicit"), + entry("hibernate.physical_naming_strategy", "com.example.Physical")); + assertThat(hibernateProperties) + .doesNotContainKeys("hibernate.ejb.naming_strategy"); + } + + @Test + public void useNewIdGeneratorMappingsDefaultHibernate4() throws Exception { + JpaProperties properties = load(HibernateVersion.V4); + Map hibernateProperties = properties + .getHibernateProperties(mockStandaloneDataSource()); + assertThat(hibernateProperties) + .doesNotContainKey(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS); + } + + @Test + public void useNewIdGeneratorMappingsDefaultHibernate5() throws Exception { + JpaProperties properties = load(HibernateVersion.V5); + Map hibernateProperties = properties + .getHibernateProperties(mockStandaloneDataSource()); + assertThat(hibernateProperties) + .containsEntry(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "false"); + } + + @Test + public void useNewIdGeneratorMappingsTrue() throws Exception { + JpaProperties properties = load(HibernateVersion.V5, + "spring.jpa.hibernate.use-new-id-generator-mappings:true"); + Map hibernateProperties = properties + .getHibernateProperties(mockStandaloneDataSource()); + assertThat(hibernateProperties) + .containsEntry(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "true"); + } + + @SuppressWarnings("unchecked") + private DataSource mockStandaloneDataSource() throws SQLException { + DataSource ds = mock(DataSource.class); + given(ds.getConnection()).willThrow(SQLException.class); + return ds; + } + + private JpaProperties load(HibernateVersion hibernateVersion, String... environment) { + HibernateVersion.setRunning(hibernateVersion); + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(ctx, environment); + ctx.register(TestConfiguration.class); + ctx.refresh(); + this.context = ctx; + JpaProperties properties = this.context.getBean(JpaProperties.class); + return properties; + } + + @Configuration + @EnableConfigurationProperties(JpaProperties.class) + static class TestConfiguration { + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/test/City.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/test/City.java index 1f808274f8..4708c9e6e6 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/test/City.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/test/City.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2016 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. diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index b542b5a42f..14eb91ab0f 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -65,6 +65,7 @@ 2.0.0 1.3.2 10.12.1.1 + 1.6.1 3.1.2 2.10.1 1.50.2 @@ -79,7 +80,7 @@ 1.4.191 1.3 3.6.1 - 4.3.11.Final + 5.1.0.Final 5.2.4.Final 2.4.5 2.3.13 @@ -806,6 +807,11 @@ de.flapdoodle.embed.mongo ${embedded-mongo.version} + + dom4j + dom4j + ${dom4j.version} + io.dropwizard.metrics metrics-core diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml index 89ae668ab9..427eabc9d9 100644 --- a/spring-boot-samples/pom.xml +++ b/spring-boot-samples/pom.xml @@ -45,6 +45,7 @@ spring-boot-sample-devtools spring-boot-sample-flyway spring-boot-sample-hateoas + spring-boot-sample-hibernate4 spring-boot-sample-hornetq spring-boot-sample-hypermedia spring-boot-sample-hypermedia-gson diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/pom.xml b/spring-boot-samples/spring-boot-sample-hibernate4/pom.xml new file mode 100755 index 0000000000..282c375331 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-samples + 1.4.0.BUILD-SNAPSHOT + + spring-boot-sample-hibernate4 + Spring Boot Hibernate 4 Sample + Spring Boot Hibernate 4 Sample + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + 4.3.11.Final + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + com.h2database + h2 + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.springframework + springloaded + ${spring-loaded.version} + + + + + + diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/SampleHibernate4Application.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/SampleHibernate4Application.java new file mode 100644 index 0000000000..ec3a08f35e --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/SampleHibernate4Application.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SampleHibernate4Application { + + public static void main(String[] args) throws Exception { + SpringApplication.run(SampleHibernate4Application.class, args); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/City.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/City.java new file mode 100644 index 0000000000..0e1feeb26b --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/City.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.domain; + +import java.io.Serializable; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class City implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private String state; + + @Column(nullable = false) + private String country; + + @Column(nullable = false) + private String map; + + protected City() { + } + + public City(String name, String country) { + super(); + this.name = name; + this.country = country; + } + + public String getName() { + return this.name; + } + + public String getState() { + return this.state; + } + + public String getCountry() { + return this.country; + } + + public String getMap() { + return this.map; + } + + @Override + public String toString() { + return getName() + "," + getState() + "," + getCountry(); + } +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/Hotel.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/Hotel.java new file mode 100644 index 0000000000..7cca7fae04 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/Hotel.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.domain; + +import java.io.Serializable; +import java.util.Set; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.NaturalId; + +@Entity +public class Hotel implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(optional = false) + @NaturalId + private City city; + + @Column(nullable = false) + @NaturalId + private String name; + + @Column(nullable = false) + private String address; + + @Column(nullable = false) + private String zip; + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "hotel") + private Set reviews; + + protected Hotel() { + } + + public Hotel(City city, String name) { + this.city = city; + this.name = name; + } + + public City getCity() { + return this.city; + } + + public String getName() { + return this.name; + } + + public String getAddress() { + return this.address; + } + + public String getZip() { + return this.zip; + } +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/HotelSummary.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/HotelSummary.java new file mode 100644 index 0000000000..49f4392997 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/HotelSummary.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.domain; + +public interface HotelSummary { + + City getCity(); + + String getName(); + + Double getAverageRating(); + + default Integer getAverageRatingRounded() { + return getAverageRating() == null ? null : (int) Math.round(getAverageRating()); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/Rating.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/Rating.java new file mode 100644 index 0000000000..51f82422d8 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/Rating.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.domain; + +public enum Rating { + TERRIBLE, POOR, AVERAGE, GOOD, EXCELLENT, +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/RatingCount.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/RatingCount.java new file mode 100644 index 0000000000..6765220f39 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/RatingCount.java @@ -0,0 +1,25 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.domain; + +public interface RatingCount { + + Rating getRating(); + + long getCount(); + +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/Review.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/Review.java new file mode 100644 index 0000000000..e5c331418e --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/Review.java @@ -0,0 +1,130 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.domain; + +import java.io.Serializable; +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.springframework.util.Assert; + +@Entity +public class Review implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(optional = false) + private Hotel hotel; + + @Column(nullable = false, name = "idx") + private int index; + + @Column(nullable = false) + @Enumerated(EnumType.ORDINAL) + private Rating rating; + + @Column(name= "CHECK_IN_DATE", nullable = false) + @Temporal(TemporalType.DATE) + private Date checkInDate; + + @Column(name = "TRIP_TYPE", nullable = false) + @Enumerated(EnumType.ORDINAL) + private TripType tripType; + + @Column(nullable = false) + private String title; + + @Column(nullable = false, length = 5000) + private String details; + + protected Review() { + } + + public Review(Hotel hotel, int index, ReviewDetails details) { + Assert.notNull(hotel, "Hotel must not be null"); + Assert.notNull(details, "Details must not be null"); + this.hotel = hotel; + this.index = index; + this.rating = details.getRating(); + this.checkInDate = details.getCheckInDate(); + this.tripType = details.getTripType(); + this.title = details.getTitle(); + this.details = details.getDetails(); + } + + public Hotel getHotel() { + return this.hotel; + } + + public int getIndex() { + return this.index; + } + + public Rating getRating() { + return this.rating; + } + + public void setRating(Rating rating) { + this.rating = rating; + } + + public Date getCheckInDate() { + return this.checkInDate; + } + + public void setCheckInDate(Date checkInDate) { + this.checkInDate = checkInDate; + } + + public TripType getTripType() { + return this.tripType; + } + + public void setTripType(TripType tripType) { + this.tripType = tripType; + } + + public String getTitle() { + return this.title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDetails() { + return this.details; + } + + public void setDetails(String details) { + this.details = details; + } +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/ReviewDetails.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/ReviewDetails.java new file mode 100644 index 0000000000..9639909750 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/ReviewDetails.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.domain; + +import java.io.Serializable; +import java.util.Date; + +public class ReviewDetails implements Serializable { + + private static final long serialVersionUID = 1L; + + private Rating rating; + + private Date checkInDate; + + private TripType tripType; + + private String title; + + private String details; + + public ReviewDetails() { + } + + public Rating getRating() { + return this.rating; + } + + public void setRating(Rating rating) { + this.rating = rating; + } + + public Date getCheckInDate() { + return this.checkInDate; + } + + public void setCheckInDate(Date checkInDate) { + this.checkInDate = checkInDate; + } + + public TripType getTripType() { + return this.tripType; + } + + public void setTripType(TripType tripType) { + this.tripType = tripType; + } + + public String getTitle() { + return this.title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDetails() { + return this.details; + } + + public void setDetails(String details) { + this.details = details; + } +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/TripType.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/TripType.java new file mode 100644 index 0000000000..5b342e1f86 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/domain/TripType.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.domain; + +public enum TripType { + BUSINESS, COUPLES, FAMILY, FRIENDS, SOLO +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/CityRepository.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/CityRepository.java new file mode 100644 index 0000000000..23dab9e94d --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/CityRepository.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.service; + +import sample.hibernate4.domain.City; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.Repository; + +interface CityRepository extends Repository { + + Page findAll(Pageable pageable); + + Page findByNameContainingAndCountryContainingAllIgnoringCase(String name, + String country, Pageable pageable); + + City findByNameAndCountryAllIgnoringCase(String name, String country); + +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/CitySearchCriteria.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/CitySearchCriteria.java new file mode 100644 index 0000000000..9b356e9822 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/CitySearchCriteria.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.service; + +import java.io.Serializable; + +import org.springframework.util.Assert; + +public class CitySearchCriteria implements Serializable { + + private static final long serialVersionUID = 1L; + + private String name; + + public CitySearchCriteria() { + } + + public CitySearchCriteria(String name) { + Assert.notNull(name, "Name must not be null"); + this.name = name; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/CityService.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/CityService.java new file mode 100644 index 0000000000..abe0fbefc5 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/CityService.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.service; + +import sample.hibernate4.domain.City; +import sample.hibernate4.domain.HotelSummary; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface CityService { + + Page findCities(CitySearchCriteria criteria, Pageable pageable); + + City getCity(String name, String country); + + Page getHotels(City city, Pageable pageable); + +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/CityServiceImpl.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/CityServiceImpl.java new file mode 100644 index 0000000000..f2ad078a7d --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/CityServiceImpl.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.service; + +import sample.hibernate4.domain.City; +import sample.hibernate4.domain.HotelSummary; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +@Component("cityService") +@Transactional +class CityServiceImpl implements CityService { + + private final CityRepository cityRepository; + + private final HotelRepository hotelRepository; + + public CityServiceImpl(CityRepository cityRepository, + HotelRepository hotelRepository) { + this.cityRepository = cityRepository; + this.hotelRepository = hotelRepository; + } + + @Override + public Page findCities(CitySearchCriteria criteria, Pageable pageable) { + + Assert.notNull(criteria, "Criteria must not be null"); + String name = criteria.getName(); + + if (!StringUtils.hasLength(name)) { + return this.cityRepository.findAll(null); + } + + String country = ""; + int splitPos = name.lastIndexOf(","); + + if (splitPos >= 0) { + country = name.substring(splitPos + 1); + name = name.substring(0, splitPos); + } + + return this.cityRepository + .findByNameContainingAndCountryContainingAllIgnoringCase(name.trim(), + country.trim(), pageable); + } + + @Override + public City getCity(String name, String country) { + Assert.notNull(name, "Name must not be null"); + Assert.notNull(country, "Country must not be null"); + return this.cityRepository.findByNameAndCountryAllIgnoringCase(name, country); + } + + @Override + public Page getHotels(City city, Pageable pageable) { + Assert.notNull(city, "City must not be null"); + return this.hotelRepository.findByCity(city, pageable); + } +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/HotelRepository.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/HotelRepository.java new file mode 100644 index 0000000000..92e897bcb4 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/HotelRepository.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.service; + +import java.util.List; + +import sample.hibernate4.domain.City; +import sample.hibernate4.domain.Hotel; +import sample.hibernate4.domain.HotelSummary; +import sample.hibernate4.domain.RatingCount; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.Repository; + +interface HotelRepository extends Repository { + + Hotel findByCityAndName(City city, String name); + + @Query("select h.city as city, h.name as name, avg(r.rating) as averageRating " + + "from Hotel h left outer join h.reviews r where h.city = ?1 group by h") + Page findByCity(City city, Pageable pageable); + + @Query("select r.rating as rating, count(r) as count " + + "from Review r where r.hotel = ?1 group by r.rating order by r.rating DESC") + List findRatingCounts(Hotel hotel); + +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/HotelService.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/HotelService.java new file mode 100644 index 0000000000..8edbd5fe9e --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/HotelService.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.service; + +import sample.hibernate4.domain.City; +import sample.hibernate4.domain.Hotel; +import sample.hibernate4.domain.Review; +import sample.hibernate4.domain.ReviewDetails; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface HotelService { + + Hotel getHotel(City city, String name); + + Page getReviews(Hotel hotel, Pageable pageable); + + Review getReview(Hotel hotel, int index); + + Review addReview(Hotel hotel, ReviewDetails details); + + ReviewsSummary getReviewSummary(Hotel hotel); + +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/HotelServiceImpl.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/HotelServiceImpl.java new file mode 100644 index 0000000000..65ffdca1c3 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/HotelServiceImpl.java @@ -0,0 +1,98 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import sample.hibernate4.domain.City; +import sample.hibernate4.domain.Hotel; +import sample.hibernate4.domain.Rating; +import sample.hibernate4.domain.RatingCount; +import sample.hibernate4.domain.Review; +import sample.hibernate4.domain.ReviewDetails; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +@Component("hotelService") +@Transactional +class HotelServiceImpl implements HotelService { + + private final HotelRepository hotelRepository; + + private final ReviewRepository reviewRepository; + + public HotelServiceImpl(HotelRepository hotelRepository, + ReviewRepository reviewRepository) { + this.hotelRepository = hotelRepository; + this.reviewRepository = reviewRepository; + } + + @Override + public Hotel getHotel(City city, String name) { + Assert.notNull(city, "City must not be null"); + Assert.hasLength(name, "Name must not be empty"); + return this.hotelRepository.findByCityAndName(city, name); + } + + @Override + public Page getReviews(Hotel hotel, Pageable pageable) { + Assert.notNull(hotel, "Hotel must not be null"); + return this.reviewRepository.findByHotel(hotel, pageable); + } + + @Override + public Review getReview(Hotel hotel, int reviewNumber) { + Assert.notNull(hotel, "Hotel must not be null"); + return this.reviewRepository.findByHotelAndIndex(hotel, reviewNumber); + } + + @Override + public Review addReview(Hotel hotel, ReviewDetails details) { + Review review = new Review(hotel, 1, details); + return this.reviewRepository.save(review); + } + + @Override + public ReviewsSummary getReviewSummary(Hotel hotel) { + List ratingCounts = this.hotelRepository.findRatingCounts(hotel); + return new ReviewsSummaryImpl(ratingCounts); + } + + private static class ReviewsSummaryImpl implements ReviewsSummary { + + private final Map ratingCount; + + public ReviewsSummaryImpl(List ratingCounts) { + this.ratingCount = new HashMap(); + for (RatingCount ratingCount : ratingCounts) { + this.ratingCount.put(ratingCount.getRating(), ratingCount.getCount()); + } + } + + @Override + public long getNumberOfReviewsWithRating(Rating rating) { + Long count = this.ratingCount.get(rating); + return count == null ? 0 : count; + } + } +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/ReviewRepository.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/ReviewRepository.java new file mode 100644 index 0000000000..b63e7e61e9 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/ReviewRepository.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.service; + +import sample.hibernate4.domain.Hotel; +import sample.hibernate4.domain.Review; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.Repository; + +interface ReviewRepository extends Repository { + + Page findByHotel(Hotel hotel, Pageable pageable); + + Review findByHotelAndIndex(Hotel hotel, int index); + + Review save(Review review); + +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/ReviewsSummary.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/ReviewsSummary.java new file mode 100644 index 0000000000..e9aca1b255 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/service/ReviewsSummary.java @@ -0,0 +1,25 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.service; + +import sample.hibernate4.domain.Rating; + +public interface ReviewsSummary { + + long getNumberOfReviewsWithRating(Rating rating); + +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/web/SampleController.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/web/SampleController.java new file mode 100644 index 0000000000..6caf593329 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/java/sample/hibernate4/web/SampleController.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.web; + +import sample.hibernate4.service.CityService; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +public class SampleController { + + @Autowired + private CityService cityService; + + @RequestMapping("/") + @ResponseBody + @Transactional(readOnly = true) + public String helloWorld() { + return this.cityService.getCity("Bath", "UK").getName(); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/resources/application.properties new file mode 100644 index 0000000000..330237435d --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.h2.console.enabled=true + +logging.level.org.hibernate.SQL=debug diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/main/resources/import.sql b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/resources/import.sql new file mode 100644 index 0000000000..fbd484608b --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/main/resources/import.sql @@ -0,0 +1,186 @@ +-- +-- Sample dataset containing a number of Hotels in various Cities across the world. The reviews are entirely fictional :) +-- + +-- ================================================================================================= +-- AUSTRALIA + +-- Brisbane +insert into city(country, name, state, map) values ('Australia', 'Brisbane', 'Queensland', '-27.470933, 153.023502') +insert into hotel(city_id, name, address, zip) values (1, 'Conrad Treasury Place', 'William & George Streets', '4001') + +-- Melbourne +insert into city(country, name, state, map) values ('Australia', 'Melbourne', 'Victoria', '-37.813187, 144.96298') +insert into hotel(city_id, name, address, zip) values (2, 'The Langham', '1 Southgate Ave, Southbank', '3006') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (2, 0, '2005-05-10', 2, 4, 'Pretty average', 'I stayed in 2005, the hotel was nice enough but nothing special.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (2, 1, '2006-01-12', 4, 2, 'Bright hotel with big rooms', 'This hotel has a fantastic lovely big windows. The room we stayed in had lots of space. Recommended.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (2, 2, '2006-05-25', 3, 1, 'Pretty good', 'I liked this hotel and would stay again.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (2, 3, '2009-01-20', 3, 2, 'Nice clean rooms', 'The rooms are maintained to a high standard and very clean, the bathroom was spotless!!') + +-- Sydney +insert into city(country, name, state, map) values ('Australia', 'Sydney', 'New South Wales', '-33.868901, 151.207091') +insert into hotel(city_id, name, address, zip) values (3, 'Swissotel', '68 Market Street', '2000') + + +-- ================================================================================================= +-- CANADA + +-- Montreal +insert into city(country, name, state, map) values ('Canada', 'Montreal', 'Quebec', '45.508889, -73.554167') +insert into hotel(city_id, name, address, zip) values (4, 'Ritz Carlton', '1228 Sherbrooke St', 'H3G1H6') + + +-- ================================================================================================= +-- ISRAEL + +-- Tel Aviv +insert into city(country, name, state, map) values ('Israel', 'Tel Aviv', '', '32.066157, 34.777821') +insert into hotel(city_id, name, address, zip) values (5, 'Hilton Tel Aviv', 'Independence Park', '63405') + + +-- ================================================================================================= +-- JAPAN + +-- Tokyo +insert into city(country, name, state, map) values ('Japan', 'Tokyo', '', '35.689488, 139.691706') +insert into hotel(city_id, name, address, zip) values (6, 'InterContinental Tokyo Bay', 'Takeshiba Pier', '105') + + +-- ================================================================================================= +-- SPAIN + +-- Barcelona +insert into city(country, name, state, map) values ('Spain', 'Barcelona', 'Catalunya', '41.387917, 2.169919') +insert into hotel(city_id, name, address, zip) values (7, 'Hilton Diagonal Mar', 'Passeig del Taulat 262-264', '08019') + +-- ================================================================================================= +-- SWITZERLAND + +-- Neuchatel +insert into city(country, name, state, map) values ('Switzerland', 'Neuchatel', '', '46.992979, 6.931933') +insert into hotel(city_id, name, address, zip) values (8, 'Hotel Beaulac', ' Esplanade Leopold-Robert 2', '2000') + + +-- ================================================================================================= +-- UNITED KINGDOM + +-- Bath +insert into city(country, name, state, map) values ('UK', 'Bath', 'Somerset', '51.381428, -2.357454') +insert into hotel(city_id, name, address, zip) values (9, 'The Bath Priory Hotel', 'Weston Road', 'BA1 2XT') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 0, '2000-01-23', 4, 1, 'A lovely hotel', 'We stayed here after a wedding and it was fantastic. Recommend to all.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 1, '2000-08-04', 3, 1, 'Very special', 'A very special hotel with lovely staff.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 2, '2001-01-01', 2, 4, 'Nice but too hot', 'Stayed during the summer heat wave (exceptional for England!) and the room was very hot. Still recommended.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 3, '2002-01-20', 3, 1, 'Big rooms and a great view', 'Considering how central this hotel is the rooms are a very good size.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 4, '2002-11-03', 2, 1, 'Good but pricey', 'A nice hotel but be prepared to pay over the odds for your stay.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 5, '2003-09-18', 4, 1, 'Fantastic place', 'Just lovely.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 6, '2004-03-21', 4, 3, 'A very special place', 'I stayed here in 2004 and found it to be very relaxing, a nice pool and good gym is cherry on the cake.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 7, '2004-04-10', 0, 0, 'Terrible', 'I complained after I was told I could not check out after 11pm. Ridiculous!!!') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 8, '2004-12-20', 4, 4, 'A perfect location', 'Central location makes this a perfect hotel. Be warned though, it''s not cheap.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 9, '2005-04-19', 3, 2, 'Expensive but worth it', 'Dig deep into your pockets and enjoy this lovely City and fantastic hotel.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 10, '2005-05-21', 4, 1, 'The best hotel in the area', 'Top hotel in the area, would not stay anywhere else.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 11, '2005-11-17', 4, 2, 'Lovely hotel, fantastic grounds', 'The garden upkeep run into thousands (perhaps explaining why the rooms are so much) but so lovely and relaxing.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 12, '2006-01-04', 3, 4, 'Gorgeous Top Quality Hotel', 'Top draw stuff.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 13, '2006-01-21', 4, 1, 'Fabulous Hotel and Restaurant', 'The food at this hotel is second to none, try the peppered steak!') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 14, '2006-01-29', 4, 4, 'Feels like home', 'A lovely home away from home.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 15, '2006-03-21', 1, 1, 'Far too expensive', 'Overpriced, Overpriced, Overpriced!!') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 16, '2006-05-10', 4, 1, 'Excellent Hotel, Wonderful Staff', 'The staff went out of their way to help us after we missed our last train home, organising a Taxi back to Newport even after we had checked out.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 17, '2007-09-11', 3, 2, 'The perfect retreat', 'If you want a relaxing stay, this is the place.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 18, '2008-06-01', 3, 3, 'Lovely stay, fantastic staff', 'As other reviews have noted, the staff in this hotel really are the best in Bath.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 19, '2009-05-14', 4, 2, 'Can''t Wait to go back', 'We will stay again for sure.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 20, '2010-04-26', 4, 1, 'Amazing Hotel', 'We won a trip here after entering a competition. Not sure we would pay the full price but such a wonderful place.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (9, 21, '2010-10-26', 2, 2, 'Dissapointed', 'The pool was closed, the chief was ill, the staff were rude my wallet is bruised!') +insert into hotel(city_id, name, address, zip) values (9, 'Bath Travelodge', 'Rossiter Road, Widcombe Basin', 'BA2 4JP') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (10, 0, '2002-08-21', 0, 2, 'Terrible hotel', 'One of the worst hotels that I have ever stayed in.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (10, 1, '2003-01-28', 0, 0, 'Rude and unpleasant staff', 'The staff refused to help me with any aspect of my stay, I will not stay here again.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (10, 2, '2004-06-17', 1, 0, 'Below par', 'Don''t stay here!!') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (10, 3, '2005-07-12', 0, 1, 'Small and Unpleasant', 'The room was far too small and felt unclean. Not recommended.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (10, 4, '2006-01-07', 1, 4, 'Cheap if you are not fussy', 'This hotel has some rough edges but I challenge you to find somewhere cheaper.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (10, 5, '2006-01-13', 0, 2, 'Terrible', 'Just terrible!') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (10, 6, '2006-03-25', 0, 0, 'Smelly and dirty room', 'My room smelt of damp and I found the socks of the previous occupant under my bed.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (10, 7, '2006-04-09', 0, 4, 'Grim', 'Grim. I would try elsewhere before staying here.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (10, 8, '2006-08-01', 1, 3, 'Very Noisy', 'Building work during the day and a disco at night. Good grief!') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (10, 9, '2009-01-03', 1, 4, 'Tired and falling down', 'This hotel is in serious need of refurbishment, the windows are rotting, the paintwork is tired and the carpets are from the 1970s.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (10, 10, '2009-07-20', 0, 0, 'Not suitable for human habitation', 'I would not put my dog up in this hotel.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (10, 11, '2010-05-20', 1, 0, 'Conveient for the railway', 'Average place but useful if you need to commute') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (10, 12, '2010-01-22', 2, 2, 'Not as bad as the reviews', 'Some of the reviews seem a bit harsh, it''s not too bad for the price.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (10, 13, '2011-01-10', 3, 1, 'Reburished and nice', 'Looks like this hotel has had a major facelift. If you have stayed before 2011 perhaps it''s time to give this hotel another try. Very good value for money and pretty nice.') + +-- London +insert into city(country, name, state, map) values ('UK', 'London', '', '51.500152, -0.126236') +insert into hotel(city_id, name, address, zip) values (10, 'Melia White House', 'Albany Street', 'NW1 3UP') + +-- Southampton +insert into city(country, name, state, map) values ('UK', 'Southampton', 'Hampshire', '50.902571, -1.397238') +insert into hotel(city_id, name, address, zip) values (11, 'Chilworth Manor', 'The Cottage, Southampton Business Park', 'SO16 7JF') + + +-- ================================================================================================= +-- USA + +-- Atlanta +insert into city(country, name, state, map) values ('USA', 'Atlanta', 'GA', '33.748995, -84.387982') +insert into hotel(city_id, name, address, zip) values (12, 'Marriott Courtyard', 'Tower Place, Buckhead', '30305') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (13, 0, '2009-01-20', 3, 0, 'Better than most', 'Most other hotels is this area are a bit ropey, this one is actually pretty good.') +insert into hotel(city_id, name, address, zip) values (12, 'Ritz Carlton', 'Peachtree Rd, Buckhead', '30326') +insert into hotel(city_id, name, address, zip) values (12, 'Doubletree', 'Tower Place, Buckhead', '30305') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (15, 0, '2006-01-12', 2, 3, 'No fuss hotel', 'Cheap, no fuss hotel. Good if you are travelling on business and just need a place to stay.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (15, 1, '2009-01-20', 2, 2, 'Nice area but small rooms', 'The area felt nice and safe but the rooms are a little on the small side') + +-- Chicago +insert into city(country, name, state, map) values ('USA', 'Chicago', 'IL', '41.878114, -87.629798') +insert into hotel(city_id, name, address, zip) values (13, 'Hotel Allegro', '171 West Randolph Street', '60601') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (16, 0, '2009-12-15', 3, 2, 'Cheap and Recommended', 'Good value for money, can''t really fault it.') + +-- Eau Claire +insert into city(country, name, state, map) values ('USA', 'Eau Claire', 'WI', '44.811349, -91.498494') +insert into hotel(city_id, name, address, zip) values (14, 'Sea Horse Inn', '2106 N Clairemont Ave', '54703') +insert into hotel(city_id, name, address, zip) values (14, 'Super 8 Eau Claire Campus Area', '1151 W Macarthur Ave', '54701') + +-- Hollywood +insert into city(country, name, state, map) values ('USA', 'Hollywood', 'FL', '26.011201, -80.14949') +insert into hotel(city_id, name, address, zip) values (15, 'Westin Diplomat', '3555 S. Ocean Drive', '33019') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (19, 0, '2006-01-11', 0, 0, 'Avoid', 'The hotel has a very bad reputation. I would avoid it if I were you.') + +-- Miami +insert into city(country, name, state, map) values ('USA', 'Miami', 'FL', '25.788969, -80.226439') +insert into hotel(city_id, name, address, zip) values (16, 'Conrad Miami', '1395 Brickell Ave', '33131') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (20, 0, '2010-01-09', 3, 2, 'Close to the local attractions', 'Fantastic access to all the local attractions mean you won''t mind the small rooms.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (20, 1, '2010-09-10', 2, 2, 'Good value and friendly', 'Not expensive and very welcoming staff. I would stay again.') + +-- Melbourne +insert into city(country, name, state, map) values ('USA', 'Melbourne', 'FL', '28.083627, -80.608109') +insert into hotel(city_id, name, address, zip) values (17, 'Radisson Suite Hotel Oceanfront', '3101 North Hwy', '32903') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (21, 0, '2005-06-15', 3, 3, 'A very nice hotel', 'I can''t fault this hotel and I have stayed here many times. Always friendly staff and lovely atmosphere.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (21, 1, '2006-01-20', 2, 4, 'Comfortable and good value', 'To complaints at all.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (21, 2, '2007-08-21', 3, 1, 'Above average', 'Better than a lot of hotels in the area and not too pricey.') + +-- New York +insert into city(country, name, state, map) values ('USA', 'New York', 'NY', '40.714353, -74.005973') +insert into hotel(city_id, name, address, zip) values (18, 'W Union Hotel', 'Union Square, Manhattan', '10011') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (22, 0, '2002-01-19', 0, 1, 'Too noisy, too small', 'The city never sleeps and neither will you if you say here. The rooms are small and the sound insulation is poor!') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (22, 1, '2004-03-10', 1, 4, 'Overpriced', 'Far too much money for such a tiny room!') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (22, 2, '2007-04-11', 2, 0, 'So so, nothing special', 'Not brilliant but not too bad either.') +insert into hotel(city_id, name, address, zip) values (18, 'W Lexington Hotel', 'Lexington Ave, Manhattan', '10011') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (23, 0, '2004-07-21', 3, 2, 'Excellent location', 'So close to the heart of the city. Recommended.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (23, 1, '2006-05-20', 3, 1, 'Very nice', 'I can''t fault this hotel, clean, good location and nice staff.') +insert into hotel(city_id, name, address, zip) values (18, '70 Park Avenue Hotel', '70 Park Avenue', '10011') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (24, 0, '2003-11-10', 4, 1, 'Great!!', 'I own this hotel and I think it is pretty darn good.') + +-- Palm Bay +insert into city(country, name, state, map) values ('USA', 'Palm Bay', 'FL', '28.034462, -80.588665') +insert into hotel(city_id, name, address, zip) values (19, 'Jameson Inn', '890 Palm Bay Rd NE', '32905') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (25, 0, '2005-10-20', 3, 2, 'Fantastical', 'This is the BEST hotel in Palm Bay, not complaints at all.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (25, 1, '2006-01-12', 4, 1, 'Top marks', 'I rate this hotel 5 stars, the best in the area by miles.') + +-- San Francisco +insert into city(country, name, state, map) values ('USA', 'San Francisco', 'CA', '37.77493, -122.419415') +insert into hotel(city_id, name, address, zip) values (20, 'Marriot Downtown', '55 Fourth Street', '94103') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (26, 0, '2006-07-02', 2, 3, 'Could be better', 'I stayed in late 2006 with work, the room was very small and the restaurant does not stay open very late.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (26, 1, '2008-07-01', 1, 4, 'Brrrr cold!', 'My room was freezing cold, I would not recommend this place.') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (26, 2, '2009-01-05', 3, 2, 'Nice for money', 'You can''t really go wrong here for the money. There may be better places to stay but not for this price.') + +-- Washington +insert into city(country, name, state, map) values ('USA', 'Washington', 'DC', '38.895112, -77.036366') +insert into hotel(city_id, name, address, zip) values (21, 'Hotel Rouge', '1315 16th Street NW', '20036') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (27, 0, '2000-01-29', 0, 2, 'Never again', 'I will never ever stay here again!! They wanted extra cash to get fresh batteries for the TV remote') +insert into review(hotel_id, idx, check_in_date, rating, trip_type, title, details) values (27, 1, '2006-02-20', 0, 0, 'Avoid', 'This place is the pits, they charged us twice for a single night stay. I only got refunded after contacting my credit card company.') diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/test/java/sample/hibernate4/SampleDataJpaApplicationTests.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/test/java/sample/hibernate4/SampleDataJpaApplicationTests.java new file mode 100644 index 0000000000..0dbe60486c --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/test/java/sample/hibernate4/SampleDataJpaApplicationTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4; + +import java.lang.management.ManagementFactory; + +import javax.management.ObjectName; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Integration test to run the application. + * + * @author Oliver Gierke + * @author Dave Syer + */ +@RunWith(SpringRunner.class) +@SpringBootTest +// Enable JMX so we can test the MBeans (you can't do this in a properties file) +@TestPropertySource(properties = { "spring.jmx.enabled:true", + "spring.datasource.jmx-enabled:true" }) +@ActiveProfiles("scratch") +// Separate profile for web tests to avoid clashing databases +public class SampleDataJpaApplicationTests { + + @Autowired + private WebApplicationContext context; + + private MockMvc mvc; + + @Before + public void setUp() { + this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + } + + @Test + public void testHome() throws Exception { + + this.mvc.perform(get("/")).andExpect(status().isOk()) + .andExpect(content().string("Bath")); + } + + @Test + public void testJmx() throws Exception { + assertThat(ManagementFactory.getPlatformMBeanServer() + .queryMBeans(new ObjectName("jpa.sample:type=ConnectionPool,*"), null)) + .hasSize(1); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/test/java/sample/hibernate4/service/CityRepositoryIntegrationTests.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/test/java/sample/hibernate4/service/CityRepositoryIntegrationTests.java new file mode 100644 index 0000000000..cb4e5a49d1 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/test/java/sample/hibernate4/service/CityRepositoryIntegrationTests.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.service; + +import org.junit.Test; +import org.junit.runner.RunWith; +import sample.hibernate4.domain.City; +import sample.hibernate4.service.CityRepository; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link CityRepository}. + * + * @author Oliver Gierke + */ +@RunWith(SpringRunner.class) +@SpringBootTest +public class CityRepositoryIntegrationTests { + + @Autowired + CityRepository repository; + + @Test + public void findsFirstPageOfCities() { + + Page cities = this.repository.findAll(new PageRequest(0, 10)); + assertThat(cities.getTotalElements()).isGreaterThan(20L); + } +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/test/java/sample/hibernate4/service/HotelRepositoryIntegrationTests.java b/spring-boot-samples/spring-boot-sample-hibernate4/src/test/java/sample/hibernate4/service/HotelRepositoryIntegrationTests.java new file mode 100644 index 0000000000..05365ce953 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/test/java/sample/hibernate4/service/HotelRepositoryIntegrationTests.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-2016 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 sample.hibernate4.service; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import sample.hibernate4.domain.City; +import sample.hibernate4.domain.Hotel; +import sample.hibernate4.domain.HotelSummary; +import sample.hibernate4.domain.Rating; +import sample.hibernate4.domain.RatingCount; +import sample.hibernate4.service.CityRepository; +import sample.hibernate4.service.HotelRepository; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link HotelRepository}. + * + * @author Oliver Gierke + */ +@RunWith(SpringRunner.class) +@SpringBootTest +public class HotelRepositoryIntegrationTests { + + @Autowired + CityRepository cityRepository; + @Autowired + HotelRepository repository; + + @Test + public void executesQueryMethodsCorrectly() { + City city = this.cityRepository + .findAll(new PageRequest(0, 1, Direction.ASC, "name")).getContent() + .get(0); + assertThat(city.getName()).isEqualTo("Atlanta"); + + Page hotels = this.repository.findByCity(city, + new PageRequest(0, 10, Direction.ASC, "name")); + Hotel hotel = this.repository.findByCityAndName(city, + hotels.getContent().get(0).getName()); + assertThat(hotel.getName()).isEqualTo("Doubletree"); + + List counts = this.repository.findRatingCounts(hotel); + assertThat(counts).hasSize(1); + assertThat(counts.get(0).getRating()).isEqualTo(Rating.AVERAGE); + assertThat(counts.get(0).getCount()).isGreaterThan(1L); + } +} diff --git a/spring-boot-samples/spring-boot-sample-hibernate4/src/test/resources/application-scratch.properties b/spring-boot-samples/spring-boot-sample-hibernate4/src/test/resources/application-scratch.properties new file mode 100644 index 0000000000..782bef1f1a --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hibernate4/src/test/resources/application-scratch.properties @@ -0,0 +1,2 @@ +spring.datasource.name=scratchdb +spring.jmx.default-domain=jpa.sample \ No newline at end of file diff --git a/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringNamingStrategy.java b/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringNamingStrategy.java index 5a1bc70180..7ab2e36855 100644 --- a/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringNamingStrategy.java +++ b/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringNamingStrategy.java @@ -16,7 +16,9 @@ package org.springframework.boot.orm.jpa.hibernate; +import org.hibernate.Hibernate; import org.hibernate.cfg.ImprovedNamingStrategy; +import org.hibernate.cfg.NamingStrategy; import org.hibernate.internal.util.StringHelper; import org.springframework.util.Assert; @@ -31,7 +33,10 @@ import org.springframework.util.StringUtils; * @author Phillip Webb * @see "http://stackoverflow.com/questions/7689206/ejb3namingstrategy-vs-improvednamingstrategy-foreign-key-naming" * @since 1.2.0 + * @deprecated since 1.4.0 since {@link NamingStrategy} is no longer used by + * {@link Hibernate}. Consider using {@link SpringPhysicalNamingStrategy} */ +@Deprecated @SuppressWarnings("serial") public class SpringNamingStrategy extends ImprovedNamingStrategy { diff --git a/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringPhysicalNamingStrategy.java b/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringPhysicalNamingStrategy.java new file mode 100644 index 0000000000..927f02de94 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringPhysicalNamingStrategy.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2016 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.boot.orm.jpa.hibernate; + +import java.util.Locale; + +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.naming.PhysicalNamingStrategy; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; + +/** + * Hibernate {@link PhysicalNamingStrategy} that follows Spring recommended naming + * conventions. + * + * @author Phillip Webb + * @since 1.4.0 + */ +public class SpringPhysicalNamingStrategy implements PhysicalNamingStrategy { + + @Override + public Identifier toPhysicalCatalogName(Identifier name, + JdbcEnvironment jdbcEnvironment) { + return apply(name); + } + + @Override + public Identifier toPhysicalSchemaName(Identifier name, + JdbcEnvironment jdbcEnvironment) { + return apply(name); + } + + @Override + public Identifier toPhysicalTableName(Identifier name, + JdbcEnvironment jdbcEnvironment) { + return apply(name); + } + + @Override + public Identifier toPhysicalSequenceName(Identifier name, + JdbcEnvironment jdbcEnvironment) { + return apply(name); + } + + @Override + public Identifier toPhysicalColumnName(Identifier name, + JdbcEnvironment jdbcEnvironment) { + return apply(name); + } + + private Identifier apply(Identifier name) { + if (name == null) { + return null; + } + StringBuilder text = new StringBuilder(name.getText().replace('.', '_')); + for (int i = 1; i < text.length() - 1; i++) { + if (isDashRequired(text.charAt(i - 1), text.charAt(i), text.charAt(i + 1))) { + text.insert(i++, '_'); + } + } + return new Identifier(text.toString().toLowerCase(Locale.ROOT), name.isQuoted()); + } + + private boolean isDashRequired(char before, char current, char after) { + return Character.isLowerCase(before) && Character.isUpperCase(current) + && Character.isLowerCase(after); + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/orm/jpa/hibernate/SpringPhysicalNamingStrategyTests.java b/spring-boot/src/test/java/org/springframework/boot/orm/jpa/hibernate/SpringPhysicalNamingStrategyTests.java new file mode 100644 index 0000000000..0ce35b59a2 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/orm/jpa/hibernate/SpringPhysicalNamingStrategyTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2016 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.boot.orm.jpa.hibernate; + +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.BootstrapServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.service.ServiceRegistry; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SpringPhysicalNamingStrategy}. + * + * @author Phillip Webb + */ +public class SpringPhysicalNamingStrategyTests { + + private Metadata metadata; + + @Before + public void setup() throws Exception { + MetadataSources metadataSources = new MetadataSources(); + metadataSources.addAnnotatedClass(TelephoneNumber.class); + StandardServiceRegistry serviceRegistry = getServiceRegistry(metadataSources); + this.metadata = metadataSources.getMetadataBuilder(serviceRegistry) + .applyPhysicalNamingStrategy(new SpringPhysicalNamingStrategy()).build(); + } + + private StandardServiceRegistry getServiceRegistry(MetadataSources metadataSources) { + ServiceRegistry registry = metadataSources.getServiceRegistry(); + return new StandardServiceRegistryBuilder((BootstrapServiceRegistry) registry) + .applySetting(AvailableSettings.DIALECT, H2Dialect.class).build(); + } + + @Test + public void tableNameShouldBeLowercaseUnderscore() throws Exception { + PersistentClass binding = this.metadata + .getEntityBinding(TelephoneNumber.class.getName()); + assertThat(binding.getTable().getQuotedName()).isEqualTo("telephone_number"); + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/orm/jpa/hibernate/TelephoneNumber.java b/spring-boot/src/test/java/org/springframework/boot/orm/jpa/hibernate/TelephoneNumber.java new file mode 100644 index 0000000000..1729c912dd --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/orm/jpa/hibernate/TelephoneNumber.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2016 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.boot.orm.jpa.hibernate; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +/** + * Simple entity used in {@link SpringPhysicalNamingStrategyTests}. + * + * @author Phillip Webb + */ +@Entity +public class TelephoneNumber { + + @Id + @GeneratedValue + Long id; + + String areaCode; + + String number; + +}