From 701dcb2593a528b6f4c899144075d289e5b298a2 Mon Sep 17 00:00:00 2001 From: Gustavo Martin Morcuende <gu.martinm@gmail.com> Date: Fri, 1 Jul 2016 22:58:59 +0200 Subject: [PATCH] More JPA stuff --- SpringJava/JPA/context.xml | 53 ++++++++++ SpringJava/JPA/pom.xml | 16 +-- .../de/spring/persistence/example/domain/Ad.java | 108 ++++++++++----------- .../persistence/example/domain/AdDescription.java | 100 +++++++++++++++++++ .../repository/AdDescriptionRepository.java | 13 +++ .../example/repository/AdRepository.java | 13 ++- .../de/spring/rest/controllers/AdController.java | 10 +- .../datasource-configuration.xml | 33 ++----- .../spring-configuration/jpa-configuration.xml | 4 +- SpringJava/JPA/src/main/webapp/WEB-INF/web.xml | 20 ++++ 10 files changed, 278 insertions(+), 92 deletions(-) create mode 100644 SpringJava/JPA/context.xml create mode 100644 SpringJava/JPA/src/main/java/de/spring/persistence/example/domain/AdDescription.java create mode 100644 SpringJava/JPA/src/main/java/de/spring/persistence/example/repository/AdDescriptionRepository.java diff --git a/SpringJava/JPA/context.xml b/SpringJava/JPA/context.xml new file mode 100644 index 0000000..e053ee3 --- /dev/null +++ b/SpringJava/JPA/context.xml @@ -0,0 +1,53 @@ +<?xml version='1.0' encoding='utf-8'?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You 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. +--> +<!-- The contents of this file will be loaded for each web application --> +<Context> + + <!-- Default set of monitored resources --> + <WatchedResource>WEB-INF/web.xml</WatchedResource> + + <!-- Uncomment this to disable session persistence across Tomcat restarts --> + <!-- + <Manager pathname="" /> + --> + + <!-- Uncomment this to enable Comet connection tacking (provides events + on session expiration as well as webapp lifecycle) --> + <!-- + <Valve className="org.apache.catalina.valves.CometConnectionManagerValve" /> + --> + + + + <Resource auth="Container" + driverClassName="com.mysql.jdbc.Driver" + logAbandoned="true" + maxTotal="300" + maxIdle="5" + maxWaitMillis="5000" + name="jdbc/example" + password="root" + removeAbandonedOnBorrow="true" + removeAbandonedOnMaintenance="true" + type="javax.sql.DataSource" + url="jdbc:mysql://localhost:3306/mybatis_example?allowMultiQueries=true&autoReconnect=true&characterEncoding=UTF-8" + username="root" + validationQuery="select 1" /> + + +</Context> diff --git a/SpringJava/JPA/pom.xml b/SpringJava/JPA/pom.xml index c8a763a..1c800b3 100644 --- a/SpringJava/JPA/pom.xml +++ b/SpringJava/JPA/pom.xml @@ -13,8 +13,8 @@ <url>https://gumartinm.name/</url> </organization> <scm> - <developerConnection>scm:git:http://git.gumartinm.name/Spring/JPA</developerConnection> - <url>http://git.gumartinm.name/Spring/JPA</url> + <developerConnection>scm:git:https://git.gumartinm.name/Spring/JPA</developerConnection> + <url>https://git.gumartinm.name/Spring/JPA</url> </scm> <properties> @@ -104,18 +104,18 @@ </exclusion> </exclusions> </dependency> + <dependency> + <groupId>javax.inject</groupId> + <artifactId>javax.inject</artifactId> + <version>1</version> + </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2.2</version> </dependency> - - <dependency> - <groupId>com.mchange</groupId> - <artifactId>c3p0</artifactId> - <version>0.9.5.2</version> - </dependency> + <!-- Required JPA dependencies with hibernate --> <dependency> diff --git a/SpringJava/JPA/src/main/java/de/spring/persistence/example/domain/Ad.java b/SpringJava/JPA/src/main/java/de/spring/persistence/example/domain/Ad.java index e29ec68..6784538 100644 --- a/SpringJava/JPA/src/main/java/de/spring/persistence/example/domain/Ad.java +++ b/SpringJava/JPA/src/main/java/de/spring/persistence/example/domain/Ad.java @@ -2,43 +2,88 @@ package de.spring.persistence.example.domain; import java.io.Serializable; import java.time.LocalDateTime; +import java.util.Set; import javax.persistence.Column; +import javax.persistence.Convert; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; +import javax.persistence.NamedNativeQueries; +import javax.persistence.NamedNativeQuery; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToMany; import javax.persistence.Table; import javax.validation.constraints.Max; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import de.spring.persistence.converters.LocalDateTimeAttributeConverter; + @Entity -@Table(schema = "mybatis_example") +@Table(name="ad", schema="mybatis_example") +// 1. Named query is JPL. It is portable. +// 2. Instead of annotating the domain class we should be using @Query annotation at the query method +// because it should be cleaner :) +// So you'd better use @Query. +//http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.at-query +//See: de.spring.persistence.example.repository.AdRepository +@NamedQueries( + { + @NamedQuery( + name="Ad.findByIdQuery", + query="select a FROM AD a WHERE a.id = :id") + } + +) +// 1. Native query IS NOT JPL. It is not portable and it is written directly in the native language +// of the store. We can use special features at the cost of portability. +// 2. Instead of annotating the domain class we should be using @Query annotation at the query method +// because it should be cleaner :) +// So you'd better use @Query. +// http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.at-query +// See: de.spring.persistence.example.repository.AdRepository +@NamedNativeQueries( + { + @NamedNativeQuery( + name="Ad.findByIdNativeQuery", + query="SELECT * FROM AD WHERE AD.ID = :id", + resultClass=Ad.class) + } +) public class Ad implements Serializable { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) - @Column(name = "id", updatable = false, nullable = false) + @Column(name="id", updatable=false, nullable=false) private Long id; + @OneToMany(mappedBy="ad", fetch=FetchType.LAZY, targetEntity=AdDescription.class) + private Set<AdDescription> adDescriptions; + @Max(60) + @Column(name="company_id") private Long companyId; @Max(40) - @Column + @Column(name="company_categ_id") private Long companyCategId; - @Size(min=2, max=240) - @Column + @Size(min=2, max=255) + @Column(name="ad_mobile_image") private String adMobileImage; @NotNull - @Column(nullable = false) + @Convert(converter=LocalDateTimeAttributeConverter.class) + @Column(name="created_at", nullable=false) private LocalDateTime createdAt; @NotNull - @Column(nullable = false) + @Convert(converter=LocalDateTimeAttributeConverter.class) + @Column(name="updated_at", nullable = false) private LocalDateTime updatedAt; // It will be used by JPA when filling the property fields with data coming from data base. @@ -79,53 +124,4 @@ public class Ad implements Serializable { public LocalDateTime getUpdatedAt() { return updatedAt; } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((adMobileImage == null) ? 0 : adMobileImage.hashCode()); - result = prime * result + ((companyCategId == null) ? 0 : companyCategId.hashCode()); - result = prime * result + ((companyId == null) ? 0 : companyId.hashCode()); - result = prime * result + ((createdAt == null) ? 0 : createdAt.hashCode()); - result = prime * result + ((updatedAt == null) ? 0 : updatedAt.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Ad other = (Ad) obj; - if (adMobileImage == null) { - if (other.adMobileImage != null) - return false; - } else if (!adMobileImage.equals(other.adMobileImage)) - return false; - if (companyCategId == null) { - if (other.companyCategId != null) - return false; - } else if (!companyCategId.equals(other.companyCategId)) - return false; - if (companyId == null) { - if (other.companyId != null) - return false; - } else if (!companyId.equals(other.companyId)) - return false; - if (createdAt == null) { - if (other.createdAt != null) - return false; - } else if (!createdAt.equals(other.createdAt)) - return false; - if (updatedAt == null) { - if (other.updatedAt != null) - return false; - } else if (!updatedAt.equals(other.updatedAt)) - return false; - return true; - } } diff --git a/SpringJava/JPA/src/main/java/de/spring/persistence/example/domain/AdDescription.java b/SpringJava/JPA/src/main/java/de/spring/persistence/example/domain/AdDescription.java new file mode 100644 index 0000000..a3b7d16 --- /dev/null +++ b/SpringJava/JPA/src/main/java/de/spring/persistence/example/domain/AdDescription.java @@ -0,0 +1,100 @@ +package de.spring.persistence.example.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; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.validation.constraints.Max; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +@Entity +@Table(name="ad_description", schema="mybatis_example") +public class AdDescription implements Serializable { + + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Column(name="id", updatable=false, nullable=false) + private Long id; + + @NotNull + @ManyToOne(optional=false) + @JoinColumn(name="ad_id", referencedColumnName="id") + private Ad ad; + + @NotNull + @Max(60) + @Column(name="language_id") + private Long languageId; + + @NotNull + @Size(min=2, max=255) + @Column(name="ad_name") + private String adName; + + @NotNull + @Size(min=2, max=255) + @Column(name="ad_description") + private String adDescription; + + @NotNull + @Size(min=2, max=500) + @Column(name="ad_mobile_text") + private String adMobileText; + + @NotNull + @Size(min=2, max=3000) + @Column(name="ad_link") + private String adLink; + + // It will be used by JPA when filling the property fields with data coming from data base. + protected AdDescription() { + + } + + // It will be used by my code (for example by Unit Tests) + public AdDescription(Long id, Ad ad, Long languageId, String adName, String adDescription, + String adMobileText, String adLink) { + this.id = id; + this.languageId = languageId; + this.ad = ad; + this.adName = adName; + this.adDescription = adDescription; + this.adMobileText = adMobileText; + this.adLink = adLink; + } + + public Long getId() { + return id; + } + + public Ad getAd() { + return ad; + } + + public Long getLanguageId() { + return languageId; + } + + public String getAdName() { + return adName; + } + + public String getAdDescription() { + return adDescription; + } + + public String getAdMobileText() { + return adMobileText; + } + + public String getAdLink() { + return adLink; + } +} diff --git a/SpringJava/JPA/src/main/java/de/spring/persistence/example/repository/AdDescriptionRepository.java b/SpringJava/JPA/src/main/java/de/spring/persistence/example/repository/AdDescriptionRepository.java new file mode 100644 index 0000000..120b2c8 --- /dev/null +++ b/SpringJava/JPA/src/main/java/de/spring/persistence/example/repository/AdDescriptionRepository.java @@ -0,0 +1,13 @@ +package de.spring.persistence.example.repository; + +import org.springframework.data.domain.Page; +import org.springframework.data.repository.PagingAndSortingRepository; + +import de.spring.persistence.example.domain.Ad; +import de.spring.persistence.example.domain.AdDescription; + +public interface AdDescriptionRepository extends PagingAndSortingRepository<AdDescription, Long> { + + // Custom Query method + Page<AdDescription> findByAd(Ad ad); +} diff --git a/SpringJava/JPA/src/main/java/de/spring/persistence/example/repository/AdRepository.java b/SpringJava/JPA/src/main/java/de/spring/persistence/example/repository/AdRepository.java index 8230c43..b625fed 100644 --- a/SpringJava/JPA/src/main/java/de/spring/persistence/example/repository/AdRepository.java +++ b/SpringJava/JPA/src/main/java/de/spring/persistence/example/repository/AdRepository.java @@ -1,9 +1,20 @@ package de.spring.persistence.example.repository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; import de.spring.persistence.example.domain.Ad; public interface AdRepository extends PagingAndSortingRepository<Ad, Long> { - + + // Named Native Query (using the native language of the store) It is not portable. + // See de.spring.persistence.example.domain.Ad + @Query(value="SELECT * FROM AD WHERE AD.ID = :id", nativeQuery=true) + Ad findByIdNativeQuery(@Param("id") Long id); + + // Named Query (using JPL) It is portable. + // See de.spring.persistence.example.domain.Ad + @Query("SELECT * FROM AD WHERE AD.ID = :id") + Ad findByIdQuery(@Param("id") Long id); } diff --git a/SpringJava/JPA/src/main/java/de/spring/rest/controllers/AdController.java b/SpringJava/JPA/src/main/java/de/spring/rest/controllers/AdController.java index 16a61f9..ae0e29f 100644 --- a/SpringJava/JPA/src/main/java/de/spring/rest/controllers/AdController.java +++ b/SpringJava/JPA/src/main/java/de/spring/rest/controllers/AdController.java @@ -1,5 +1,7 @@ package de.spring.rest.controllers; +import javax.inject.Inject; + import org.resthub.web.controller.RepositoryBasedRestController; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -9,7 +11,13 @@ import de.spring.persistence.example.repository.AdRepository; @RestController @RequestMapping("/ads/") -public class AdController extends RepositoryBasedRestController<Ad, Long, AdRepository>{ +public class AdController extends RepositoryBasedRestController<Ad, Long, AdRepository> { + @Override + @Inject + public void setRepository(AdRepository repository) { + this.repository = repository; + } + // I do not have to do anything here because all I need is implemented by RepositoryBasedRestController :) } diff --git a/SpringJava/JPA/src/main/resources/spring-configuration/datasource-configuration.xml b/SpringJava/JPA/src/main/resources/spring-configuration/datasource-configuration.xml index 0951744..babff20 100644 --- a/SpringJava/JPA/src/main/resources/spring-configuration/datasource-configuration.xml +++ b/SpringJava/JPA/src/main/resources/spring-configuration/datasource-configuration.xml @@ -3,42 +3,27 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xmlns:tx="http://www.springframework.org/schema/tx" + xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/tx - http://www.springframework.org/schema/tx/spring-tx.xsd"> + http://www.springframework.org/schema/tx/spring-tx.xsd + http://www.springframework.org/schema/jee + http://www.springframework.org/schema/jee/spring-jee.xsd"> <!-- enable the configuration of transactional behavior based on annotations --> <tx:annotation-driven transaction-manager="transactionManager"/> - <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> - <property name="user" value="db2inst1"/> - <property name="password" value="db2inst1"/> - <property name="driverClass" value="com.ibm.db2.jcc.DB2Driver"/> - <!-- - If you are dropping like me (by means of some firewall) IPV6 connections and you feel - during the first SLQ connection as if there is a huge lag and you are using - *NIX, you could use this system property -Djava.net.preferIPv4Stack=true - in order to stop using IPV6 from JVM. - The JVM tries to find out if IPV6 is available by means of opening a random - AF_INET6 POSIX socket. - --> - <property name="jdbcUrl" value="jdbc:db2://localhost:50000/velabd:allowNextOnExhaustedResultSet=1;currentSchema=CLOSURE;"/> - <property name="initialPoolSize" value="5"/> - <property name="maxPoolSize" value="20"/> - <property name="minPoolSize" value="10"/> - <property name="acquireIncrement" value="1"/> - <property name="acquireRetryAttempts" value="5"/> - <property name="acquireRetryDelay" value="1000"/> - <property name="automaticTestTable" value="con_test"/> - <property name="checkoutTimeout" value="5000"/> - </bean> + + <!-- Using external provided datasource (in my case the one from Tomcat) --> + <jee:jndi-lookup id="dataSource" jndi-name="jdbc/example" expected-type="javax.sql.DataSource"/> + <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> - <property name="entityManagerFactory" ref="jpaEntityManagerFactory" /> + <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> </beans> diff --git a/SpringJava/JPA/src/main/resources/spring-configuration/jpa-configuration.xml b/SpringJava/JPA/src/main/resources/spring-configuration/jpa-configuration.xml index 286d067..1a16615 100644 --- a/SpringJava/JPA/src/main/resources/spring-configuration/jpa-configuration.xml +++ b/SpringJava/JPA/src/main/resources/spring-configuration/jpa-configuration.xml @@ -19,7 +19,7 @@ <context:property-placeholder location="classpath:jpa.properties" /> - <bean id="jpaEntityManagerFactory" + <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="jpaVendorAdapter"> @@ -31,7 +31,7 @@ <property name="packagesToScan" value="de.spring.persistence.**.domain" /> </bean> - <jpa:repositories entity-manager-factory-ref="jpaEntityManagerFactory" + <jpa:repositories entity-manager-factory-ref="entityManagerFactory" base-package="de.spring.persistence.**.repository" transaction-manager-ref="transactionManager" /> diff --git a/SpringJava/JPA/src/main/webapp/WEB-INF/web.xml b/SpringJava/JPA/src/main/webapp/WEB-INF/web.xml index 01de2a8..31173b6 100644 --- a/SpringJava/JPA/src/main/webapp/WEB-INF/web.xml +++ b/SpringJava/JPA/src/main/webapp/WEB-INF/web.xml @@ -37,5 +37,25 @@ <!-- REQUIRED PATTERN BY swagger-ui. IT DOESN'T WORK WITH ANY OTHER o.O --> <url-pattern>/*</url-pattern> </servlet-mapping> + + + <!-- + Se serializan objetos Entity al vuelo. + + 1. JACKSON irá accediendo a todas las propiedades de un objeto Entity. + 2. Según vaya accediendo se irán haciendo queries a base de datos. NO ANTES porque se hace en + modo lazy, lo que quiere decir que tenemos el objeto Entity pero sus datos (lo que se obtiene + después de hacer las queries) están vacÃos. Hasta que no se accede a una propiedad determinada + del objeto Entity no se hará realmente la query en base de datos. + --> + <filter> + <filter-name>OpenEntityManagerInViewFilter</filter-name> + <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class> + </filter> + <filter-mapping> + <filter-name>OpenEntityManagerInViewFilter</filter-name> + <url-pattern>/*</url-pattern> + </filter-mapping> + </web-app> -- 2.1.4