I'm representing Appointments in an entity bean which have a startTime and endTime of type Calendar. This is stored using JPA as TIMESTAMP. If I edit the Appointment object's start / end time it updates on the object itself during the session but it's not updated within the database itself. Other elements such as the Appointment's description successfully update in the database.. It's only he start and end time which are not.
Entity class (this is indirectly updated in the database with EntityManager.merge():
package mcknighte.entity;
import java.io.Serializable;
import java.util.Calendar;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Temporal;
import static javax.persistence.TemporalType.TIMESTAMP;
import javax.validation.constraints.NotNull;
import mcknighte.common.Convertable;
/**
* Appointment entity class, to represent an Appointment within the database
* and throughout the application
*
* #author Edward McKnight (UP608985)
* #see Client
* #see AppointmentFacade
* #see AppointmentService
* #see AppointmentController
* #since 2017
* #version 1.0
*/
#Entity
public class Appointment implements Serializable, Convertable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#Temporal(TIMESTAMP)
private Calendar startTime;
#NotNull
#Temporal(TIMESTAMP)
private Calendar endTime;
#NotNull
private String description;
#NotNull
#ManyToOne(targetEntity = Client.class)
private Client creator;
#NotNull
#OneToMany(targetEntity = Client.class)
private List<Client> attendees;
/**
* Constructor
*/
public Appointment() {
this.startTime = Calendar.getInstance();
this.endTime = Calendar.getInstance();
}
/**
* Get the attendees for the appointment
*
* #return a list of attendees for the appointment
*/
public List<Client> getAttendees() {
return attendees;
}
/**
* Set the attendees for the appointment
*
* #param attendees a list of attendees for the appointment
*/
public void setAttendees(List<Client> attendees) {
this.attendees = attendees;
}
/**
* Get the creator of the appointment
*
* #return the creator of the appointment
*/
public Client getCreator() {
return creator;
}
/**
* Set the creator of the appointment
*
* #param creator the creator of the appointment
*/
public void setCreator(Client creator) {
this.creator = creator;
}
/**
* Get the description for the appointment
*
* #return the description for the appointment
*/
public String getDescription() {
return description;
}
/**
* Set the description for the appointment
*
* #param description the description for the appointment
*/
public void setDescription(String description) {
this.description = description;
}
/**
* Get the end time of the appointment
*
* #return the end time of the appointment
*/
public Calendar getEndTime() {
return endTime;
}
/**
* Set the end time of the appointment
*
* #param end the end time of the appointment
*/
public void setEndTime(Calendar end) {
this.endTime = end;
}
/**
* Get the start time of the appointment
*
* #return the start time of the appointment
*/
public Calendar getStartTime() {
return startTime;
}
/**
* Set the start time of the appointment
*
* #param start the start time of the appointment
*/
public void setStartTime(Calendar start) {
this.startTime = start;
}
/**
* Get the ID for the appointment
*
* #return the ID for the appointment
*/
#Override
public Long getId() {
return id;
}
/**
* Set the ID for the appointment
*
* #param id the ID for the appointment
*/
public void setId(Long id) {
this.id = id;
}
/**
* Hash code
*
* #return int
*/
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
/**
* Equals
*
* #param object the object to compare to
* #return whether or not this equals the object being compared to
*/
#Override
public boolean equals(Object object) {
if (!(object instanceof Appointment)) {
return false;
}
Appointment other = (Appointment) object;
return !((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id)));
}
/**
* Represent this object as a string
*
* #return a string representation of this object
*/
#Override
public String toString() {
return "mcknighte.entity.Appointment[ id=" + id + " ]";
}
}
The issue has been resolved after finding another StackOverflow question and reading the Oracle documentation.
All that needed to be done was to add an #Mutable annotation to each of the Calendar properties, as Date and Calendar are immutable by default.
Related
I have an application that queries a lot of data and then inserts it into specific tables. Due to the fact that there are many records, the application is taking a long time to complete. The funny thing is that the times it has been executed, it always ends after 1 hour showing SpringApplicationShutdownHook, this It causes not all the records to be processed and many need to be added.
I leave some screenshots of the log , please help.
aplication starts at 16:08:14
shutdown message on log at 17:08:13
#Java8, #Springboot, #Postgree , #Oracle
I tried to review the configuration files, but I only found default hikari configuration with times but they are not 1 hour.
[aplication.properties]
logging.config=classpath:logback.xml
spring.config.location=D:/sise/dev/config.properties
postgre.jpa.show-sql=false
postgre.jpa.hibernate.ddl-auto=update
postgre.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
log of database connection is closing after 1 hour:
2023-01-09 17:08:13 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-3 - Shutdown initiated...
2023-01-09 17:08:13 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-3 - Shutdown completed.
2023-01-09 17:08:13 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-2 - Shutdown initiated...
2023-01-09 17:08:13 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-2 - Shutdown completed.
2023-01-09 17:08:13 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
2023-01-09 17:08:13 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.
[com.zaxxer.hikari.HikariConfig]
/*
* Copyright (C) 2013, 2014 Brett Wooldridge
*
* 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 com.zaxxer.hikari;
import com.codahale.metrics.health.HealthCheckRegistry;
import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
import com.zaxxer.hikari.util.PropertyElf;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.AccessControlException;
import java.sql.Connection;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadLocalRandom;
import static com.zaxxer.hikari.util.UtilityElf.getNullIfEmpty;
import static com.zaxxer.hikari.util.UtilityElf.safeIsAssignableFrom;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
#SuppressWarnings({"SameParameterValue"})
public class HikariConfig implements HikariConfigMXBean
{
private static final Logger LOGGER = LoggerFactory.getLogger(HikariConfig.class);
private static final char[] ID_CHARACTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
private static final long CONNECTION_TIMEOUT = SECONDS.toMillis(30);
private static final long VALIDATION_TIMEOUT = SECONDS.toMillis(5);
private static final long SOFT_TIMEOUT_FLOOR = Long.getLong("com.zaxxer.hikari.timeoutMs.floor", 250L);
private static final long IDLE_TIMEOUT = MINUTES.toMillis(10);
private static final long MAX_LIFETIME = MINUTES.toMillis(30);
private static final long DEFAULT_KEEPALIVE_TIME = 0L;
private static final int DEFAULT_POOL_SIZE = 10;
private static boolean unitTest = false;
// Properties changeable at runtime through the HikariConfigMXBean
//
private volatile String catalog;
private volatile long connectionTimeout;
private volatile long validationTimeout;
private volatile long idleTimeout;
private volatile long leakDetectionThreshold;
private volatile long maxLifetime;
private volatile int maxPoolSize;
private volatile int minIdle;
private volatile String username;
private volatile String password;
// Properties NOT changeable at runtime
//
private long initializationFailTimeout;
private String connectionInitSql;
private String connectionTestQuery;
private String dataSourceClassName;
private String dataSourceJndiName;
private String driverClassName;
private String exceptionOverrideClassName;
private String jdbcUrl;
private String poolName;
private String schema;
private String transactionIsolationName;
private boolean isAutoCommit;
private boolean isReadOnly;
private boolean isIsolateInternalQueries;
private boolean isRegisterMbeans;
private boolean isAllowPoolSuspension;
private DataSource dataSource;
private Properties dataSourceProperties;
private ThreadFactory threadFactory;
private ScheduledExecutorService scheduledExecutor;
private MetricsTrackerFactory metricsTrackerFactory;
private Object metricRegistry;
private Object healthCheckRegistry;
private Properties healthCheckProperties;
private long keepaliveTime;
private volatile boolean sealed;
/**
* Default constructor
*/
public HikariConfig()
{
dataSourceProperties = new Properties();
healthCheckProperties = new Properties();
minIdle = -1;
maxPoolSize = -1;
maxLifetime = MAX_LIFETIME;
connectionTimeout = CONNECTION_TIMEOUT;
validationTimeout = VALIDATION_TIMEOUT;
idleTimeout = IDLE_TIMEOUT;
initializationFailTimeout = 1;
isAutoCommit = true;
keepaliveTime = DEFAULT_KEEPALIVE_TIME;
String systemProp = System.getProperty("hikaricp.configurationFile");
if (systemProp != null) {
loadProperties(systemProp);
}
}
/**
* Construct a HikariConfig from the specified properties object.
*
* #param properties the name of the property file
*/
public HikariConfig(Properties properties)
{
this();
PropertyElf.setTargetFromProperties(this, properties);
}
/**
* Construct a HikariConfig from the specified property file name. <code>propertyFileName</code>
* will first be treated as a path in the file-system, and if that fails the
* Class.getResourceAsStream(propertyFileName) will be tried.
*
* #param propertyFileName the name of the property file
*/
public HikariConfig(String propertyFileName)
{
this();
loadProperties(propertyFileName);
}
// ***********************************************************************
// HikariConfigMXBean methods
// ***********************************************************************
/** {#inheritDoc} */
#Override
public String getCatalog()
{
return catalog;
}
/** {#inheritDoc} */
#Override
public void setCatalog(String catalog)
{
this.catalog = catalog;
}
/** {#inheritDoc} */
#Override
public long getConnectionTimeout()
{
return connectionTimeout;
}
/** {#inheritDoc} */
#Override
public void setConnectionTimeout(long connectionTimeoutMs)
{
if (connectionTimeoutMs == 0) {
this.connectionTimeout = Integer.MAX_VALUE;
}
else if (connectionTimeoutMs < SOFT_TIMEOUT_FLOOR) {
throw new IllegalArgumentException("connectionTimeout cannot be less than " + SOFT_TIMEOUT_FLOOR + "ms");
}
else {
this.connectionTimeout = connectionTimeoutMs;
}
}
/** {#inheritDoc} */
#Override
public long getIdleTimeout()
{
return idleTimeout;
}
/** {#inheritDoc} */
#Override
public void setIdleTimeout(long idleTimeoutMs)
{
if (idleTimeoutMs < 0) {
throw new IllegalArgumentException("idleTimeout cannot be negative");
}
this.idleTimeout = idleTimeoutMs;
}
/** {#inheritDoc} */
#Override
public long getLeakDetectionThreshold()
{
return leakDetectionThreshold;
}
/** {#inheritDoc} */
#Override
public void setLeakDetectionThreshold(long leakDetectionThresholdMs)
{
this.leakDetectionThreshold = leakDetectionThresholdMs;
}
/** {#inheritDoc} */
#Override
public long getMaxLifetime()
{
return maxLifetime;
}
/** {#inheritDoc} */
#Override
public void setMaxLifetime(long maxLifetimeMs)
{
this.maxLifetime = maxLifetimeMs;
}
/** {#inheritDoc} */
#Override
public int getMaximumPoolSize()
{
return maxPoolSize;
}
/** {#inheritDoc} */
#Override
public void setMaximumPoolSize(int maxPoolSize)
{
if (maxPoolSize < 1) {
throw new IllegalArgumentException("maxPoolSize cannot be less than 1");
}
this.maxPoolSize = maxPoolSize;
}
/** {#inheritDoc} */
#Override
public int getMinimumIdle()
{
return minIdle;
}
/** {#inheritDoc} */
#Override
public void setMinimumIdle(int minIdle)
{
if (minIdle < 0) {
throw new IllegalArgumentException("minimumIdle cannot be negative");
}
this.minIdle = minIdle;
}
/**
* Get the default password to use for DataSource.getConnection(username, password) calls.
* #return the password
*/
public String getPassword()
{
return password;
}
/**
* Set the default password to use for DataSource.getConnection(username, password) calls.
* #param password the password
*/
#Override
public void setPassword(String password)
{
this.password = password;
}
/**
* Get the default username used for DataSource.getConnection(username, password) calls.
*
* #return the username
*/
public String getUsername()
{
return username;
}
/**
* Set the default username used for DataSource.getConnection(username, password) calls.
*
* #param username the username
*/
#Override
public void setUsername(String username)
{
this.username = username;
}
/** {#inheritDoc} */
#Override
public long getValidationTimeout()
{
return validationTimeout;
}
/** {#inheritDoc} */
#Override
public void setValidationTimeout(long validationTimeoutMs)
{
if (validationTimeoutMs < SOFT_TIMEOUT_FLOOR) {
throw new IllegalArgumentException("validationTimeout cannot be less than " + SOFT_TIMEOUT_FLOOR + "ms");
}
this.validationTimeout = validationTimeoutMs;
}
Please help me with the logic.
Is it possible to save data in multiple entities simultaneously? I f yes then how?
Also I have to do same with the singer entity i.e. add singer data to song entity...does it require the same logic or a different one.
The code for entities is as follows:
Album entity
#Entity
#Table(name = "albums")
public class AlbumEntity extends ApplicationPersistenceEntity implements Album {
#Id
#Column(name = "album_id")
#NotNull
private long albumId;
#NotEmpty
#Column(name = "NAME")
private String albumName;
#NotEmpty
#Column(name = "Genre")
private String genre;
#Column(name = "album_release_date", columnDefinition = "DATE", nullable = false)
#DateTimeFormat(pattern = "yyyy-MM-dd")
private Date albumreleaseDate;
#ManyToOne(cascade = CascadeType.ALL, optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "singer_id", nullable = false)
private SingerEntity singer;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "album")
#Fetch(FetchMode.JOIN)
private List<SongEntity> songs;
private static final long serialVersionUID = 1L;
/**
* The constructor.
*/
public AlbumEntity() {
}
/**
* The constructor.
*
* #param albumId
* #param albumName
* #param genre
* #param albumreleaseDate
* #param singer
* #param songs
*/
public AlbumEntity(long albumId, String albumName, String genre, Date albumreleaseDate, SingerEntity singer,
List<SongEntity> songs) {
super();
this.albumId = albumId;
this.albumName = albumName;
this.genre = genre;
this.albumreleaseDate = albumreleaseDate;
this.singer = singer;
this.songs = songs;
}
/**
* #return albumId
*/
#Override
public long getAlbumId() {
return this.albumId;
}
/**
* #param albumId new value of {#link #getalbumId}.
*/
#Override
public void setAlbumId(long albumId) {
this.albumId = albumId;
}
/**
* #return albumName
*/
#Override
public String getAlbumName() {
return this.albumName;
}
/**
* #param albumName new value of {#link #getalbumName}.
*/
#Override
public void setAlbumName(String albumName) {
this.albumName = albumName;
}
/**
* #return genre
*/
#Override
public String getGenre() {
return this.genre;
}
/**
* #param genre new value of {#link #getgenre}.
*/
#Override
public void setGenre(String genre) {
this.genre = genre;
}
/**
* #return albumreleaseDate
*/
#Override
public Date getAlbumreleaseDate() {
return this.albumreleaseDate;
}
/**
* #param albumreleaseDate new value of {#link #getalbumreleaseDate}.
*/
#Override
public void setAlbumreleaseDate(Date albumreleaseDate) {
this.albumreleaseDate = albumreleaseDate;
}
/**
* #return singer
*/
public SingerEntity getSinger() {
return this.singer;
}
/**
* #param singer new value of {#link #getsinger}.
*/
public void setSinger(SingerEntity singer) {
this.singer = singer;
}
/**
* #return songs
*/
public List<SongEntity> getSongs() {
return this.songs;
}
/**
* #param songs new value of {#link #getsongs}.
*/
public void setSongs(List<SongEntity> songs) {
this.songs = songs;
}
#Override
#Transient
public Long getSingerId() {
if (this.singer == null) {
return null;
}
return this.singer.getId();
}
#Override
public void setSingerId(Long singerId) {
if (singerId == null) {
this.singer = null;
} else {
SingerEntity singerEntity = new SingerEntity();
singerEntity.setId(singerId);
this.singer = singerEntity;
}
}
#Override
public String toString() {
return "AlbumEntity [albumId=" + this.albumId + ", albumName=" + this.albumName + ", genre=" + this.genre
+ ", albumreleaseDate=" + this.albumreleaseDate + ", singer=" + this.singer + ", songs=" + this.songs + "]";
}
}
Song entity
#Entity
#Table(name = "songs")
public class SongEntity extends ApplicationPersistenceEntity implements Song {
#Id
#Column(name = "song_id")
// #GeneratedValue(strategy = GenerationType.TABLE)
private long songId;
#Column(name = "Title")
private String title;
#Column(name = "duration")
private float duration;
/**
* #return duration
*/
#Override
public float getDuration() {
return this.duration;
}
/**
* #param duration new value of {#link #getduration}.
*/
#Override
public void setDuration(float duration) {
this.duration = duration;
}
#ManyToOne(cascade = CascadeType.PERSIST, optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "singer_id", nullable = false)
private SingerEntity singer;
#ManyToOne(cascade = CascadeType.ALL, optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "album_id")
private AlbumEntity album;
private static final long serialVersionUID = 1L;
/**
* The constructor.
*/
public SongEntity() {
}
/**
* The constructor.
*
* #param songId
* #param title
* #param content
* #param singer
* #param album
*/
public SongEntity(long songId, String title, SingerEntity singer, AlbumEntity album) {
super();
this.songId = songId;
this.title = title;
this.singer = singer;
this.album = album;
}
/**
* #return songId
*/
#Override
public long getSongId() {
return this.songId;
}
/**
* #param songId new value of {#link #getsongId}.
*/
#Override
public void setSongId(long songId) {
this.songId = songId;
}
/**
* #return title
*/
#Override
public String getTitle() {
return this.title;
}
/**
* #param title new value of {#link #gettitle}.
*/
#Override
public void setTitle(String title) {
this.title = title;
}
/**
* #return singer
*/
public SingerEntity getSinger() {
return this.singer;
}
/**
* #param singer new value of {#link #getsinger}.
*/
public void setSinger(SingerEntity singer) {
this.singer = singer;
}
/**
* #return album
*/
public AlbumEntity getAlbum() {
return this.album;
}
/**
* #param album new value of {#link #getalbum}.
*/
public void setAlbum(AlbumEntity album) {
this.album = album;
}
#Override
#Transient
public Long getSingerId() {
if (this.singer == null) {
return null;
}
return this.singer.getId();
}
#Override
public void setSingerId(Long singerId) {
if (singerId == null) {
this.singer = null;
} else {
SingerEntity singerEntity = new SingerEntity();
singerEntity.setId(singerId);
this.singer = singerEntity;
}
}
#Override
#Transient
public Long getAlbumId() {
if (this.album == null) {
return null;
}
return this.album.getId();
}
#Override
public void setAlbumId(Long albumId) {
if (albumId == null) {
this.album = null;
} else {
AlbumEntity albumEntity = new AlbumEntity();
albumEntity.setId(albumId);
this.album = albumEntity;
}
}
#Override
public String toString() {
return "SongEntity [songId=" + this.songId + ", title=" + this.title + ", duration=" + this.duration + ", singer="
+ this.singer + ", album=" + this.album + "]";
}
}
Singer Entity
#Entity
#Table(name = "singers")
public class SingerEntity extends ApplicationPersistenceEntity implements Singer {
#Id
#Column(name = "singer_id")
private long singerId;
#NotEmpty
#Column(name = "singer_name")
private String singerName;
#NotEmpty
#Column(name = "Gender")
private String gender;
#Column(name = "birth_date", columnDefinition = "DATE", nullable = false)
#JsonFormat(pattern = "yyyy-MM-dd")
private Date birthDate;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "singer")
private List<SongEntity> songs;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "singer")
private List<AlbumEntity> albums;
private static final long serialVersionUID = 1L;
/**
* The constructor.
*/
public SingerEntity() {
}
/**
* The constructor.
*
* #param singerId
* #param firstname
* #param lastname
* #param gender
* #param songs
* #param albums
*/
public SingerEntity(long singerId, String singerName, String gender, Date birthDate, List<SongEntity> songs,
List<AlbumEntity> albums) {
super();
this.singerId = singerId;
this.singerName = singerName;
this.gender = gender;
this.birthDate = birthDate;
this.songs = songs;
this.albums = albums;
}
/**
* #return singerId
*/
#Override
public long getSingerId() {
return this.singerId;
}
/**
* #param singerId new value of {#link #getsingerId}.
*/
#Override
public void setSingerId(long singerId) {
this.singerId = singerId;
}
/**
* #return firstname
*/
#Override
public String getSingerName() {
return this.singerName;
}
/**
* #param singername new value of {#link #getsingername}.
*/
#Override
public void setSingerName(String singerName) {
this.singerName = singerName;
}
/**
* #return gender
*/
#Override
public String getGender() {
return this.gender;
}
/**
* #param gender new value of {#link #getgender}.
*/
#Override
public void setGender(String gender) {
this.gender = gender;
}
/**
* #return birthDate
*/
#Override
public Date getBirthDate() {
return this.birthDate;
}
/**
* #param birthDate new value of {#link #getbirthDate}.
*/
#Override
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
/**
* #return songs
*/
public List<SongEntity> getSongs() {
return this.songs;
}
/**
* #param songs new value of {#link #getsongs}.
*/
public void setSongs(List<SongEntity> songs) {
this.songs = songs;
}
/**
* #return albums
*/
public List<AlbumEntity> getAlbums() {
return this.albums;
}
/**
* #param albums new value of {#link #getalbums}.
*/
public void setAlbums(List<AlbumEntity> albums) {
this.albums = albums;
}
#Override
public String toString() {
return "SingerEntity [singerId=" + this.singerId + ", singerName=" + this.singerName + ", gender=" + this.gender
+ ", birthDate=" + this.birthDate + ", songs=" + this.songs + ", albums=" + this.albums + "]";
}
}
I want to add songs to album table.
But I am not able to figure out the logic to do this.
I want that when I send data from post man like
{
"id": 102,
"albumId": 102,
"albumName": "HS1",
"genre": "Pop",
"singerId":202,
"songs":[
{
"id": 302,
"songId": 302,
"title": "Bad blood",
"singerId": 201,
"duration": 3.76
},
{
"id": 303,
"songId": 303,
"title": "Happy",
"singerId": 202,
"duration": 3.54
}
],
"albumreleaseDate":"2019-05-16"
}
Then the data should be saved in album and song table respectively
example table
I'm confused from your example data. First, I thought it's a POST for album, because it contains all the information of the album and singerID. Then I saw it also contains all information for the song. Which would imply that the song is also to be created.
So this is what I would do:
Create an album:
Request:
POST /album
{
"id": 1,
"genre": "Pop",
"name": "SomeFancyName",
"singerId": 12,
"songs": [
3,
5,
6,
15//...
]
}
Here you will verify the existence of all songs by their ID and return 404 if a song does not exist
Response:
201 Created
Headers: Location: "/ablums/{albumId}"
Add songs to an album
Request:
PATCH /album/{albumId}/songs
{
"songs": [
7,
19,
21,
13//...
]
}
Again verify the existence of all songs by their ID and return 404 if a song does not exist.
Response:
204 No Content
Delete song from album
Same as 2. just use DELETE method instead of PATCH
Songs and Singer would get their own endpoints accordingly and you only reference them by their ids in requests. Because, when you do a POST on album and add full song data, you either also create the song and have to think about what to do if the song already exists or send unnecessary data since the id should be enough to identify the song.
Basically, you add too much complexity to one endpoint
I am writing an application which is based on spring boot and tries to store and retrieve data from PostGreSQL db in its jsonb column.
while saving the record it works fine but the moment i write basic method to find records in repository interface of it like this:-
public interface AgentProfileRepository extends CrudRepository<AgentProfileOuter,String> {
public AgentProfileOuter findByJdataPcpAgentId(String id);
}
then server starts giving this exception while restarting:-
Caused by: java.lang.IllegalStateException: Illegal attempt to dereference path source [null.jdata] of basic type
at org.hibernate.jpa.criteria.path.AbstractPathImpl.illegalDereference(AbstractPathImpl.java:98) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.jpa.criteria.path.AbstractPathImpl.get(AbstractPathImpl.java:191) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:524) ~[spring-data-jpa-1.9.4.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:478) ~[spring-data-jpa-1.9.4.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.JpaQueryCreator$PredicateBuilder.getTypedPath(JpaQueryCreator.java:300) ~[spring-data-jpa-1.9.4.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.JpaQueryCreator$PredicateBuilder.build(JpaQueryCreator.java:243) ~[spring-data-jpa-1.9.4.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.JpaQueryCreator.toPredicate(JpaQueryCreator.java:148) ~[spring-data-jpa-1.9.4.RELEASE.jar:na]
The point to wonder is when I try to find by id which is a normal numeric column in postgres it works fine but not if i try to find by a key inside json.This thing works successfully with MongoDB.
Here are the bean classes written:-
AgentProfileOuter.java
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
#Table(name = "Agentbonds")
#Entity
public class AgentProfileOuter {
#GeneratedValue(strategy=GenerationType.AUTO)
#Id
private long id;
#Convert(converter = ConverterAgent.class)
#Column(name="jdata")
private AgentProfile jdata;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public AgentProfile getJdata() {
return jdata;
}
public void setJdata(AgentProfile jdata) {
this.jdata = jdata;
}
}
AgentProfile.java
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Generated;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Table;
import org.springframework.data.annotation.Id;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
#JsonInclude(JsonInclude.Include.NON_NULL)
#Generated("org.jsonschema2pojo")
#JsonPropertyOrder({
"pcpAgentId",
"name",
"bio",
"phone",
"email",
"sms",
"imageUrl"
})
public class AgentProfile {
#JsonProperty("pcpAgentId")
private String pcpAgentId;
/*
public void setAdditionalProperties(Map<String, Object> additionalProperties) {
this.additionalProperties = additionalProperties;
}
*/
#JsonProperty("name")
private String name;
#JsonProperty("bio")
private String bio;
#JsonProperty("phone")
private String phone;
#JsonProperty("email")
private String email;
#JsonProperty("sms")
private String sms;
#JsonProperty("imageUrl")
private String imageUrl;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
/**
*
* #return
* The pcpAgentId
*/
#JsonProperty("pcpAgentId")
public String getpcpAgentId() {
return pcpAgentId;
}
/**
*
* #param pcpAgentId
* The pcpAgentId
*/
#JsonProperty("pcpAgentId")
public void setAgentId(String pcpAgentId) {
this.pcpAgentId = pcpAgentId;
}
/**
*
* #return
* The name
*/
#JsonProperty("name")
public String getName() {
return name;
}
/**
*
* #param name
* The name
*/
#JsonProperty("name")
public void setName(String name) {
this.name = name;
}
/**
*
* #return
* The bio
*/
#JsonProperty("bio")
public String getBio() {
return bio;
}
/**
*
* #param bio
* The bio
*/
#JsonProperty("bio")
public void setBio(String bio) {
this.bio = bio;
}
/**
*
* #return
* The phone
*/
#JsonProperty("phone")
public String getPhone() {
return phone;
}
/**
*
* #param phone
* The phone
*/
#JsonProperty("phone")
public void setPhone(String phone) {
this.phone = phone;
}
/**
*
* #return
* The email
*/
#JsonProperty("email")
public String getEmail() {
return email;
}
/**
*
* #param email
* The email
*/
#JsonProperty("email")
public void setEmail(String email) {
this.email = email;
}
/**
*
* #return
* The sms
*/
#JsonProperty("sms")
public String getSms() {
return sms;
}
/**
*
* #param sms
* The sms
*/
#JsonProperty("sms")
public void setSms(String sms) {
this.sms = sms;
}
/**
*
* #return
* The imageUrl
*/
#JsonProperty("imageUrl")
public String getImageUrl() {
return imageUrl;
}
/**
*
* #param imageUrl
* The imageUrl
*/
#JsonProperty("imageUrl")
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
Any help on this is greatly appreciated.
I think, it's because how both Mongo and PostGreSQL structure the data. From Mongo's point, AgentProfileOuter is one document saved in JSON format as key:value. Every field in your AgentProfile class is key for Mongo irrespective of the fact it's another/ child object. However, for PostGreSQL whole AgentProfile object is just one column of String blob, since you have not marked this class as #Entity and it does not have a primary id. So, when you try to search something like pcpAgentId=someid, it does not make any sense to PostGreSQL. This is my guess, verify by checking your data structure in PostGreSQL.
Also noticed that CrudRepository<AgentProfileOuter,String> should be like CrudRepository<AgentProfileOuter,long> since AgentProfilOuter class's primary key is long.
I'm having troubles displaying a dropdown list with the proper values. I'm using the <spring-form:select>, <spring-form:options> and <spring-form:option> tags, and I just can't get it to display the correct options. With the following code, I should only be listing "Option 2", "Option 7", and "Option 8".
*Note - I do NOT want to display every possible Enum value, but for some reason Spring seems to want to display them all. It appears to be completely ignoring the list that is provided to the <spring-form:options> tag.
JSP Tags
<spring-form:select path="selectOptions">
<spring-form:option value="" label="*** Select Option ***" />
<spring-form:options path="${availableOptions}" />
</spring-form:select>
Enum
public enum SelectOptions {
// CHECKSTYLE_OFF: LineLength
/**
* Option 1.
*/
OPTION_1(1, "Option 1"),
/**
* Option 2.
*/
OPTION_2(2, "Option 2"),
/**
* Option 3.
*/
OPTION_3(3, "Option 3"),
/**
* Option 4.
*/
OPTION_4(4, "Option 4"),
/**
* Option 5.
*/
OPTION_5(5, "Option 5"),
/**
* Option 6.
*/
OPTION_6(6, "Option 6"),
/**
* Option 7.
*/
OPTION_7(7, "Option 7"),
/**
* Option 8.
*/
OPTION_8(8, "Option 8"),
/**
* Option 9.
*/
OPTION_9(9, "Option 9"),
/**
* Option 10.
*/
OPTION_10(10, "Option 10");
private int id;
private String description;
/**
* Constructor.
*
* #param id the id
* #param description the description
*/
private SelectOptions(final int id, final String description) {
this.id = id;
this.description = description;
}
/**
* Retrieves the {#link SelectOptions} associated with the passed in id.
*
* #param id the id associated with the SelectOptions
* #return the SelectOptions
*/
public static SelectOptions getInstance(final int id) {
for (final SelectOptions selectOptions : SelectOptions.values()) {
if (selectOptions.id == id) {
return selectOptions;
}
}
throw new IllegalArgumentException("SelectOptions could not be determined with id [" + id + "]");
}
/**
* Retrieves the {#link SelectOptions} associated with the passed in description.
*
* #param description the description associated with the SelectOptions
* #return the SelectOptions
*/
public static SelectOptions getInstance(final String description) {
for (final SelectOptions selectOptions : SelectOptions.values()) {
if (selectOptions.description == description) {
return selectOptions;
}
}
throw new IllegalArgumentException("SelectOptions could not be determined with description [" + description + "]");
}
/**
* Simple Getter.
*
* #return the id
*/
public int getId() {
return this.id;
}
/**
* Simple Setter.
*
* #param id the id to set
*/
public void setId(final int id) {
this.id = id;
}
/**
* Simple Getter.
*
* #return the description
*/
public String getDescription() {
return this.description;
}
/**
* Simple Setter.
*
* #param description the description to set
*/
public void setDescription(final String description) {
this.description = description;
}
}
Controller
public class SpringController implements SpringControllerInterface {
/**
* /WEB-INF/jsp/myJSP.jsp.
*/
private static final String PAGE = "/WEB-INF/jsp/myJSP.jsp";
/**
* {#inheritDoc}
*/
#Override
public ModelAndView load(final Model model) {
final ModelAndView mav = new ModelAndView(PAGE);
final List<SelectOptions> availableOptions = this.getAvailableOptions();
mav.addObject("availableOptions", availableOptions);
return mav;
}
/**
* {#inheritDoc}
*/
#Override
public ModelAndView save(final Model model) {
final ModelAndView mav = new ModelAndView(PAGE);
// TODO
return mav;
}
/**
* {#inheritDoc}
*/
#Override
public Model getModel() {
return new ModelImpl();
}
/**
* #return a list of available options
*/
public List<SelectOptions> getAvailableOptions() {
final List<SelectOptions> availableOptions = Lists.newArrayList();
availableOptions.add(SelectOptions.OPTION_1);
availableOptions.add(SelectOptions.OPTION_7);
availableOptions.add(SelectOptions.OPTION_8);
return availableOptions;
}
}
Model
public class ModelImpl implements Model {
private SelectOptions selectOptions;
/**
* Simple Getter.
*
* #return the selectOptions
*/
#Override
public SelectOptions getSelectOptions() {
return this.selectOptions;
}
/**
* Simple Setter.
*
* #param selectOptions the selectOptions to set
*/
#Override
public void setSelectOptions(final SelectOptions selectOptions) {
this.selectOptions = selectOptions;
}
}
If you created a spring controller and you want to pass an enum to your jsp page, you can do it like this:
Enum example:
public enum Coin {
HEADS("Heads", "heads"),
TAILS("Tails", "tails");
private final String fullName;
private final String shortName;
private Coin(String fullName, String shortName) {
this.fullName = fullName;
this.shortName = shortName;
}
public String getFullName() {
return fullName;
}
public String getShortName() {
return shortName;
}
}
Passing this enum to your model:
model.addObject("coins", Coin.values());
Using it in your jsp page with form:
<form:select path="selection">
<form:options items="${coins}" itemValue="shortName" itemLabel="fullName" />
</form:select>
It looks like the solution to this problem was that I was using the attribute "path" in the <spring-form:options> tag. It should have been "items", not "path".
the corrected JSP fragment:
<spring-form:select path="selectOptions">
<spring-form:option value="" label="*** Select Option ***" />
<spring-form:options items="${availableOptions}" />
</spring-form:select>
You do not even need to use items attribute if you are using <spring-form:options> tag.
The options tag:
Renders a list of HTML 'option' tags. Sets 'selected' as appropriate based on bound value.
References Spring form tld.
So for your need I guess falling back to JSTL core with <spring-form:option/> will suffice.
I have 4 entities : Country, Region, Province, Town.
<?php
namespace Entities;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #Entity (repositoryClass="Repositories\Region")
* #Table(name="regions")
* #HasLifecycleCallbacks
*/
class Region {
/**
* #Id #Column(type="integer")
* #GeneratedValue(strategy="AUTO")
*/
private $id;
/** #Column(type="string", length=30,unique=TRUE) */
private $regionname;
/** #Column(type="boolean") */
private $active;
/**
* #ManyToOne(targetEntity="Country", inversedBy="regions")
* #JoinColumn(name="countries_id", referencedColumnName="id",nullable=FALSE)
*/
private $countries_id;
/**
* #OneToMany(targetEntity="Province", mappedBy="provinces")
*/
private $provinces;
public function __construct() {
$this->provinces = new ArrayCollection();
$this->active = true;
}
<?php
namespace Entities;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #Entity (repositoryClass="Repositories\Province")
* #Table(name="provinces")
* #HasLifecycleCallbacks
*/
class Province {
/**
* #Id #Column(type="integer")
* #GeneratedValue(strategy="AUTO")
*/
private $id;
/** #Column(type="string", length=30,unique=TRUE) */
private $provincename;
/** #Column(type="boolean") */
private $active;
/**
* #ManyToOne(targetEntity="Region", inversedBy="provinces")
* #JoinColumn(name="regions_id", referencedColumnName="id",nullable=FALSE)
*/
private $regions_id;
/**
* #OneToMany(targetEntity="Town", mappedBy="towns")
*/
private $towns;
public function __construct() {
$this->towns = new ArrayCollection();
$this->active = true;
}
<?php
namespace Entities;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #Entity (repositoryClass="Repositories\Town")
* #Table(name="towns")
* #HasLifecycleCallbacks
*/
class Town {
/**
* #Id #Column(type="integer")
* #GeneratedValue(strategy="AUTO")
*/
private $id;
/** #Column(type="string", length=30,unique=FALSE) */
private $townname;
/** #Column(type="boolean") */
private $active;
// so that we know when a user has added a town
/** #Column(type="boolean") */
private $verified;
/**
* #OneToMany(targetEntity="User", mappedBy="users")
*/
private $users;
/**
* #ManyToOne(targetEntity="Province", inversedBy="towns")
* #JoinColumn(name="provinces_id", referencedColumnName="id",nullable=FALSE)
*/
private $provinces_id;
public function __construct() {
$this->users = new ArrayCollection();
$this->active = true;
}
I want to create a query using DQL that will give me a list of towns for a given region.
To get a simple list of active towns I am using :
public function findActiveTowns($provinces_id = null)
// we can pass in a specific provinces_id if we want
{
$qb = $this->_em->createQueryBuilder();
$qb->select('a.townname, a.id')
->from('Entities\Town', 'a');
if (!is_null($provinces_id)){
$qb->where('a.provinces_id = :provinces_id AND a.active = TRUE')
->setParameter('provinces_id', $provinces_id);
} else {
$qb->where('a.active = TRUE');
}
$towns=$qb->getQuery()->getResult();
// make pairs array suitable for select lists
$options = array();
foreach ($towns as $key => $value) {
$options[$value['id']] = $value['townname'];
}
return $options;
}
Now, to get to the point. How do I set up the joins and get this working so that we can pass in a region_id and return all of the towns in the region.
In native SQL I'd do something like this :
SELECT towns.id
FROM `towns`
INNER JOIN `provinces`
INNER JOIN `regions`
WHERE regions.id =1
Thanks.
A few things first.
Don't name your fields with _id, because they are not identifiers, but relations to other objects. Join column annotation goes with the real DB name, field in object model go without.
Write/generate get/set/add methods for all fields to encapsulate them, so u can actually use them. You can't read private fields from "the outside".
As for you question, haven't tested it, but something like this should work.
class Town {
/**
* #ManyToOne(targetEntity="Province", inversedBy="towns")
* #JoinColumn(name="provinces_id", referencedColumnName="id",nullable=FALSE)
*/
private $province;
class Province {
/**
* #ManyToOne(targetEntity="Region", inversedBy="provinces")
* #JoinColumn(name="regions_id", referencedColumnName="id",nullable=FALSE)
*/
private $region;
$qb->select('a.townname, a.id')
->from('Entities\Town', 'a')
->leftJoin('a.province', 'p');
if (!is_null($provinces_id) && !is_null($region_id)){
$qb->where('a.province = :province AND a.active = TRUE')
->andWhere('p.region = :region')
->setParameter('province', $provinces_id)
->setParameter('region', $region_id);
} else {
$qb->where('a.active = TRUE');
}