Save List as JSON array in postgresql - java

I have a List private List<Lesson> lessons; and I need to save this list to PostgreSQL database column with data type _json or json[] (JSON array). But I get one of these errors:
ERROR: column "lessons" is of type json[] but the expression is of
type character varyingorERROR: malformed array literal:
"[{"id":17,"title":"Lesson 1","part":1.0}]
How correctly serialize List to get the correct format of Postgresql JSON array?

You might need to use JsonBinaryType class from com.vladmihalcea:hibernate-types library and apply json (or jsonb) to the column definition:
// LessonList.java
import com.vladmihalcea.hibernate.type.json.JsonBinaryType;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
#Entity
#Table(name = "lessons")
#Getter
#Setter
#Builder
#AllArgsConstructor
#NoArgsConstructor
#TypeDef(name = "json", typeClass = JsonBinaryType.class)
public class LessonList {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Type(type = "json")
#Column(columnDefinition = "json")
private List<Lesson> lessons;
}
SQL Script to create the table:
CREATE TABLE "lessons" (
"id" Serial NOT NULL,
"lessons" JSON NOT NULL,
"created_at" Timestamp Without Time Zone DEFAULT NOW() NOT NULL,
PRIMARY KEY ("id")
);
The rest of the classes are to provide working example:
// Lesson.java
#Getter
#Setter
#Builder
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class Lesson {
private int id;
private String title;
private double part;
}
A repository extending Spring CrudRepository interface and DAO class are trivial.
A command line runner to run test:
#Slf4j
#Component
public class TestJson implements CommandLineRunner {
#Autowired
private LessonListDao dao;
#Override
public void run(String[] args) {
List<Lesson> lessons = Arrays.asList(
Lesson.builder().id(11).title("Physics").part(1.0).build(),
Lesson.builder().id(12).title("Chemistry").part(2.0).build(),
Lesson.builder().id(13).title("Biology").part(3.0).build()
);
LessonList list = LessonList.builder().lessons(lessons).build();
LessonList result = dao.save(list);
LOGGER.info("result: " + result);
List<LessonList> all = dao.findAll();
all.forEach(a -> LOGGER.info("item #" + a.getId() + "; lessons=" + a.getLessons()));
}
}
Output:
lesson.TestJson : result: lesson.LessonList#6166aac5
lesson.TestJson : item #1; lessons=[Lesson(id=1, title=Math, part=1.0), Lesson(id=2, title=English, part=2.0), Lesson(id=3, title=Informatics, part=3.0)]
lesson.TestJson : item #2; lessons=[Lesson(id=11, title=Physics, part=1.0), Lesson(id=12, title=Chemistry, part=2.0), Lesson(id=13, title=Biology, part=3.0)]
DB Data: SELECT * FROM "public".lessons:
**id** **lessons** **created_at**
[PK] Integer json timestamp without timezone
1 [{"id":1,"title":"Math","part":1.0}, 2020-06-03 18:08:55.948007
{"id":2,"title":"English","part":2.0},
{"id":3,"title":"Informatics","part":3.0}]
2 [{"id":11,"title":"Physics","part":1.0}, 2020-06-03 18:27:06.565191
{"id":12,"title":"Chemistry","part":2.0},
{"id":13,"title":"Biology","part":3.0}]
Update
This library does not support json[] type because it could be rather redundant.
The example below uses plain JDBC + ObjectMapper to handle specific PostgreSQL array of json
Main point is that you need to use getArray method of ResultSet and then convert the String values inside PGobject to your object
ConfigurableEnvironment environment = (ConfigurableEnvironment) applicationContext.getEnvironment();
DataSource ds = DataSourceBuilder.create()
.username(environment.getProperty("ds.pgsql.username"))
.password(environment.getProperty("ds.pgsql.password"))
.url(environment.getProperty("ds.pgsql.url")).build();
try (Connection connection = ds.getConnection();
PreparedStatement ps = connection.prepareStatement("SELECT id, lessons FROM lesson");
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
int id = rs.getInt("id");
Object[] arrLessons = (Object[]) rs.getArray("lessons").getArray();
List<Lesson> jsonLessons = Arrays.stream(arrLessons)
.map(PGobject.class::cast)
.map(lesson -> convert(lesson.getValue()))
.collect(Collectors.toList());
System.out.println(id + "; converted: " + jsonLessons);
}
}
//...
private Lesson convert(String value) {
try {
return mapper.readValue(value, Lesson.class);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(e);
}
}
Output
1; converted: [Lesson(id=1, title=Math, part=0.0), Lesson(id=2, title=English, part=0.0)]
2; converted: [Lesson(id=3, title=Physics, part=0.0), Lesson(id=4, title=Chemistry, part=0.0)]

Related

repository.save function changes id of it's children entity

I'm trying to update existing entry in parent Entity and I encounter error I can't understand nor resolve.
I have two entities in a simple crud repository - Parent(User) and Children(movie). I am trying to pass a favourite movie to an user. The goal is that the movie doesn't have to be already in database, and the #PostMapping has to accept an user_id and movie name as parameters, other method uses the movie name, goes through the OMDBapi, parses data from json to fields and then gives the user at user_id the movie as a favourite. The PostMapping sort of works, because it gets the user at user_id, the movie is also added, but when the url looks like this - http://localhost:8080/users/2/fight+club the user at user_id 2 gets the movie as his favourite, but the movie gets it's id also as 2, even if it's first movie being added to repository. What I don't understand is why when I try to debug this every line of code is acting as I expect it to do -
wUser(id=2, name=Jan, favouriteMovies=[Movie(id=1, title=Fight Club, plot=An insomniac office worker and a devil-may-care soap maker form an underground fight club that evolves into much more., genre=Drama, director=David Fincher, posterURL=https://m.media-amazon.com/images/M/MV5BNDIzNDU0YzEtYzE5Ni00ZjlkLTk5ZjgtNjM3NWE4YzA3Nzk3XkEyXkFqcGdeQXVyMjUzOTY1NTc#._V1_SX300.jpg)])
but after it passes repository.save(user) line I get redirected to InvocableHandlerMethod class, into doInvoke method, into
return KotlinDetector.isSuspendingFunction(method) ? this.invokeSuspendingFunction(method, this.getBean(), args) : method.invoke(this.getBean(), args);
this line, and after that it's just deep into the rabbit hole. As I am quite an inexperienced in coding in Java, what probably can be deducted, I don't really understand nor can find solution to this problem.
The entities and controller classes below
package com.example.omdbapirest.movie;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Movie {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="movie_id")
private Integer id;
private String title;
private String plot;
private String genre;
private String director;
private String posterURL;
public Movie(String title, String plot, String genre, String director, String posterURL) {
this.title = title;
this.plot = plot;
this.genre = genre;
this.director = director;
this.posterURL = posterURL;
}
}
package com.example.omdbapirest.user;
import com.example.omdbapirest.movie.Movie;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
public class wUser {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
// #OneToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH})
#OneToMany(cascade =CascadeType.ALL)
#JoinColumn(name = "movie_id")
private List<Movie> favouriteMovies;
public wUser(String name) {
this.name = name;
}
}
UserController
package com.example.omdbapirest.user;
import com.example.omdbapirest.movie.Movie;
import com.example.omdbapirest.movie.MovieService;
import lombok.RequiredArgsConstructor;
import org.json.simple.parser.ParseException;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
#RestController
#RequestMapping("/users")
#RequiredArgsConstructor
public class UserController {
private final MovieService movieService;
private final UserRepository repository;
private final UserService service;
#GetMapping
public List<wUser> getUsers(){
return repository.findAll();
}
#PostMapping("/{id}/{moviename}")
public void addMovieAsFavorite (#PathVariable (name= "id") int id,
#PathVariable (name="moviename") String moviename)
throws ParseException{
String url = "https://www.omdbapi.com/?t="+moviename+"&apikey=30ccf40c";
wUser user = repository.getById(id);
List<Movie> movies = user.getFavouriteMovies();
List<Movie>moviesToAdd = new ArrayList<>();
Movie movie = movieService.getDataFromOMDBAsMovie(url);
movies.add(movie);
moviesToAdd.addAll(movies);
user.setFavouriteMovies(moviesToAdd);
repository.save(user);
}
}
I'm also adding MovieService class in case there is some error in the JSON parser
package com.example.omdbapirest.movie;
import lombok.RequiredArgsConstructor;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.List;
#Service
#RequiredArgsConstructor
public class MovieService {
private final MovieRepository repository;
public String getJSONFromURL(String strUrl) {
String jsonText = "";
try {
URL url = new URL(strUrl);
InputStream is = url.openStream();
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(is));
String line;
while ((line = bufferedReader.readLine()) != null) {
jsonText += line + "\n";
}
is.close();
bufferedReader.close();
} catch (Exception e) {
e.printStackTrace();
}
return jsonText;
}
public Movie getDataFromOMDBAsMovie(String strURL) throws ParseException {
String json = getJSONFromURL(strURL);
Movie movie = new Movie();
JSONParser parser = new JSONParser();
Object object = parser.parse(json);
JSONObject mainJsonObject = (JSONObject) object;
String title = (String)mainJsonObject.get("Title");
movie.setTitle(title);
String plot = (String)mainJsonObject.get("Plot");
movie.setPlot(plot);
String genre = (String)mainJsonObject.get("Genre");
movie.setGenre(genre);
String director = (String)mainJsonObject.get("Director");
movie.setDirector(director);
String posterURL = (String)mainJsonObject.get("Poster");
movie.setPosterURL(posterURL);
repository.save(movie);
return movie;
}
public Movie addMovie(Movie movie){
return repository.save(movie);
}
}
I tried adding movies to db, reworking the favourite saving class, all to no avail, I was getting different errors when not debuging, including
org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Pole nie może być NULL"MOVIE_ID"(Field cannot be NULL)
NULL not allowed for column "MOVIE_ID"; SQL statement:
update movie set movie_id=null where movie_id=? [23502-214]
and
org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Naruszenie ograniczenia Klucza Głównego lub Indeksu Unikalnego: "PRIMARY KEY ON PUBLIC.MOVIE(MOVIE_ID)(translating to- Unique Index or primary key violated)
( /* key:2 */ 2, 'David Fincher', 'Drama', 'An insomniac office worker and a devil-may-care soap maker form an underground fight club that evolves into much more.', 'https://m.media-amazon.com/images/M/MV5BNDIzNDU0YzEtYzE5Ni00ZjlkLTk5ZjgtNjM3NWE4YzA3Nzk3XkEyXkFqcGdeQXVyMjUzOTY1NTc#._V1_SX300.jpg', 'Fight Club')"
Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.MOVIE(MOVIE_ID) ( /* key:2 */ 2, 'David Fincher', 'Drama', 'An insomniac office worker and a devil-may-care soap maker form an underground fight club that evolves into much more.', 'https://m.media-amazon.com/images/M/MV5BNDIzNDU0YzEtYzE5Ni00ZjlkLTk5ZjgtNjM3NWE4YzA3Nzk3XkEyXkFqcGdeQXVyMjUzOTY1NTc#._V1_SX300.jpg', 'Fight Club')"; SQL statement:
insert into movie (director, genre, plot, posterurl, title, movie_id) values (?, ?, ?, ?, ?, ?) [23505-214]
Both of these errors appear when I try to add another movie to given user, I mean I was able to give all users 1 movie, but never more since it tries to always add the movie with id of the user
Let's focus on the relevant part of your mapping:
public class Movie {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="movie_id")
private Integer id;
}
and
public class wUser {
#OneToMany(cascade =CascadeType.ALL)
#JoinColumn(name = "movie_id")
private List<Movie> favouriteMovies;
}
The id property of Movie is mapped to the table column movie_id by the configuration in the Movie class.
But for the wUser.favouriteMovies you use #JoinColumn to make it use movie_id the join column, i.e. the column in the Movie table that references the wUser.
By this that column is mapped to two completely different values and it seems in your scenario the second one wins.
To fix this simply choose a different column for the join column. user_id might be a good choice.

invoking oracle stored procedure with List of objects

I have an oracle package with procedure
TYPE rpa_type IS RECORD (
OPNAME RPA_SUMMARY_AUDIT.OPNAME%TYPE,
PROCESSDATE RPA_SUMMARY_AUDIT.PROCESSDATE%TYPE,
SESSIONID RPA_SUMMARY_AUDIT.SESSIONID%TYPE,
TOTALCOUNT RPA_SUMMARY_AUDIT.TOTALCOUNT%TYPE,
SUCCESSCOUNT RPA_SUMMARY_AUDIT.SUCCESSCOUNT%TYPE,
FAILEDCOUNT RPA_SUMMARY_AUDIT.FAILEDCOUNT%TYPE
);
TYPE rpa_tab IS TABLE OF rpa_type INDEX BY BINARY_INTEGER;
PROCEDURE save_rpa_summary(
p_parm IN rpa_tab,
p_affiliate_code IN VARCHAR);
and descirption
PROCEDURE save_rpa_summary(
p_parm IN rpa_tab,
p_affiliate_code IN VARCHAR
) IS
p_response_code VARCHAR(500);
ver_count NUMBER;
BEGIN
SELECT 'toto' INTO p_response_code FROM dual;
FOR i IN p_parm.first .. p_parm.last
LOOP
--
INSERT INTO RPA_SUMMARY_AUDIT
(ID, OPNAME, PROCESSDATE, SESSIONID, TOTALCOUNT, SUCCESSCOUNT, FAILEDCOUNT, AFFILIATE, CRATEDDATE)
VALUES(RPA_SUMMARY_AUDIT_SEQ.nextval, p_parm(i).OPNAME, p_parm(i).PROCESSDATE, p_parm(i).SESSIONID, p_parm(i).TOTALCOUNT, p_parm(i).SUCCESSCOUNT, p_parm(i).FAILEDCOUNT, p_affiliate_code, CURRENT_DATE);
ver_count:=ver_count+1;
END LOOP;
p_response_code:=ver_count;
dbms_output.put_line(p_response_code);
EXCEPTION
WHEN PROGRAM_ERROR THEN
dbms_output.put_line('ID must be greater than zero!');
p_response_code:=-1;
WHEN no_data_found THEN
dbms_output.put_line('No such customer!');
p_response_code:=-1;
WHEN others THEN
dbms_output.put_line('Error!');
p_response_code:=-1;
END save_rpa_summary;
i can invoke it using test it using
DECLARE
v_t VARCHAR(255);
t_rpa test_pkg.rpa_tab;
BEGIN
t_rpa(1).OPNAME := 'process 1';
t_rpa(1).PROCESSDATE := 'Dec-12-2019';
t_rpa(1).SESSIONID := null;
t_rpa(1).TOTALCOUNT :=3000;
t_rpa(1).SUCCESSCOUNT :=2500;
t_rpa(1).SUCCESSCOUNT :=500;
t_rpa(2).OPNAME := 'process 2';
t_rpa(2).PROCESSDATE := 'Dec-12-2019';
t_rpa(2).SESSIONID := 'SESSION 1';
t_rpa(2).TOTALCOUNT :=2500;
t_rpa(2).SUCCESSCOUNT :=1350;
t_rpa(2).SUCCESSCOUNT :=null;
TEST_PKG.SAVE_RPA_SUMMARY(t_rpa,'ENG');
END;
i am trying to use spring data jpa to invoke this procedure or SimpleJdbcCall
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import java.io.Serializable;
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class RpaDbProcessingSumarry implements Serializable {
private static final long serialVersionUID = 1L;
private String opName ;
private String processDate;
private String sessionId;
private Long totalCount ;
private Long successCount ;
private Long failedCount ;
}
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Entity(name = "RPASUMMARYAUDIT")
#NamedStoredProcedureQueries({
#NamedStoredProcedureQuery(name = "SAVE_RPA_SUMMARY",
procedureName = "MMHUSER.TEST_PKG.SAVE_RPA_SUMMARY",
parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, name = "p_parm", type = RpaDbProcessingSumarry[].class),
#StoredProcedureParameter(mode = ParameterMode.IN, name = "p_affiliate_code", type = String.class)
})
})
public class RpaProcessProcessSummary {
#Id
private Long id;
private String name;
private String prccessDate;
private String session;
private Long totalCount;
private Long successCount;
private Long failedCount;
}
#Repository
public interface RpaProcessProcessSummaryRepositary extends CrudRepository<RpaProcessProcessSummary, Long> {
#Transactional(propagation = Propagation.REQUIRED,readOnly = false)
#Procedure(name = "SAVE_RPA_SUMMARY")
void saveRpaSummaryData( RpaDbProcessingSumarry[] p_parm, String p_affiliate_code);
}
public void call(RpaDbProcessingSumarry[] p_parm ) {
SqlParameterSource in = new MapSqlParameterSource().addValue("p_parm", p_parm).addValue("p_affiliate_code","eng");
Map<String, Object> execute = new SimpleJdbcCall(this.jdbcTemplate).withCatalogName("MMHUSER")
.withProcedureName("TEST_PKG.SAVE_RPA_SUMMARY")
.execute(in);
in both cases i am getting exception
PLS-00306: wrong number or types of arguments in call to 'SAVE_RPA_SUMMARY'
ORA-06550: line 1, column 7:
is there a way to invoke this stored procedure from spring in any way

Retrieving java objects list rather than requested objects list from database

I have a problem while I'm trying to pull data from my DB requesting to be in a specific object type. I've created query, which is fetching objects of Java Object type not the type that I need. Here is my DAO class:
import com.jackowiak.Domain.TurbinesData;
import com.jackowiak.Model.TurbineDataCSVReader;
import com.jackowiak.Utils.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.query.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import java.util.List;
public class TurbinesDaoBean {
private static final Logger LOG = LoggerFactory.getLogger(TurbinesDaoBean.class);
public List<TurbinesData> getTurbineDataFromDB(String turbineName) {
LOG.info("Initializating DB connection to get turbine data");
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
Query query = session.createQuery("select windSpeed, turbinePower from TurbinesData where turbineName = :turbineName");
query.setParameter("turbineName", turbineName);
session.getTransaction().commit();
List<TurbinesData> results = query.list();
LOG.debug("Data for turbine " + turbineName + " collected successfully");
return results;
}
}
And here is my Entity class:
#Entity
#Table(name = "TurbinesData")
public class TurbinesData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
protected long id;
#Column(nullable = false, length = 50, name = "Nazwa_turbiny")
protected String turbineName;
#Column(nullable = false, length = 20, name = "V_wiatru")
protected Double windSpeed;
#Column(nullable = false, length = 20, name = "Moc_turbiny")
protected Double turbinePower;
public TurbinesData() {
}
public TurbinesData(Double windSpeed, Double turbinePower) {
this.windSpeed = windSpeed;
this.turbinePower = turbinePower;
}
public TurbinesData(String turbineName, Double windSpeed, Double turbinePower) {
this.turbineName = turbineName;
this.windSpeed = windSpeed;
this.turbinePower = turbinePower;
}
// getters and setters
}
I would like to receive list of TurbinesData objects after executing query
Change the jpql to:
"FROM TurbinesData td WHERE td.turbineName = :turbineName"
And then use TypedQuery
EDIT:
According to your comment you want to retrieve only two fields. You need to do:
"SELECT NEW package.to.TurbinesData(td.windSpeed, td.turbinePower) FROM TurbinesData td WHERE td.turbineName = :turbineName"
Note:
Need to have proper constructor defined.
Need to use fully qualified name
You can typecast List<Object> to List<custom class>
Like.
return (List<T>) query.list();
Hope that helps.

Hibernate Query Cache issue

I've found a strange bug in my app. I've simplified it, and that is how it can be reproduced:
(I used DbUnit to create the tables and HSQLDB as a database, but that doesn't actually matter)
package test;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringReader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import org.dbunit.DatabaseUnitException;
import org.dbunit.database.DatabaseConnection;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
import org.dbunit.operation.DatabaseOperation;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.springframework.orm.hibernate3.HibernateTemplate;
public class DatabaseBugReproduction {
#Entity(name = "A")
#Table(name = "a")
public static class A {
private int id;
private Set <B> bs;
#Id
public int getId() {
return id;
}
#ManyToMany
#JoinTable(
name = "ab",
joinColumns = #JoinColumn(name = "a_id"),
inverseJoinColumns = #JoinColumn(name = "b_id")
)
public Set <B> getBs() {
return bs;
}
void setId(int id) {
this.id = id;
}
void setBs(Set <B> engines) {
this.bs = engines;
}
}
#Entity(name = "B")
#Table(name = "b")
public static class B {
private int id;
#Id
public int getId() {
return id;
}
void setId(int id) {
this.id = id;
}
}
private static SessionFactory getSessionFactory() throws SQLException, IOException, DatabaseUnitException {
String driverClass = "org.hsqldb.jdbc.JDBCDriver";
String jdbcUrl = "jdbc:hsqldb:mem:seoservertooltest";
String dbUsername = "test";
String dbPassword = "test";
String dbDialect = "org.hibernate.dialect.HSQLDialect";
Configuration config = new Configuration()//
.setProperty("hibernate.connection.driver_class", driverClass)//
.setProperty("hibernate.connection.url", jdbcUrl)//
.setProperty("hibernate.connection.username", dbUsername)//
.setProperty("hibernate.connection.password", dbPassword)//
.setProperty("hibernate.dialect", dbDialect)//
.setProperty("hibernate.hbm2ddl.auto", "create-drop")//
.setProperty("hibernate.current_session_context_class", "thread")//
.setProperty("hibernate.cache.use_query_cache", "true")//
.setProperty("hibernate.cache.use_second_level_cache", "true")//
.setProperty("hibernate.cache.region.factory_class", "net.sf.ehcache.hibernate.EhCacheRegionFactory")//
.setProperty("hibernate.cache.region_prefix", "")//
// .setProperty("hibernate.show_sql", "true")//
// .setProperty("hibernate.format_sql", "true")//
.addAnnotatedClass(A.class) //
.addAnnotatedClass(B.class) //
;
SessionFactory result = config.buildSessionFactory();
try (Connection con = DriverManager.getConnection(jdbcUrl, dbUsername, dbPassword)) {
con.createStatement().executeUpdate("SET DATABASE REFERENTIAL INTEGRITY FALSE;");
String xml = "<?xml version='1.0' encoding='UTF-8'?>"//
+ "<dataset>"//
+ "<a id='1'/>"//
+ "<b id='1'/>"//
+ "<ab a_id='1' b_id='1' />"//
+ "</dataset>";
final IDatabaseConnection dbCon = new DatabaseConnection(con);
try {
final FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();
final IDataSet dataSet = builder.build(new StringReader(xml));
DatabaseOperation.CLEAN_INSERT.execute(dbCon, dataSet);
} finally {
dbCon.close();
}
}
return result;
}
public static void main(String[] args) throws Exception {
HibernateTemplate hibTemplate = new HibernateTemplate(getSessionFactory());
hibTemplate.setCacheQueries(true);
System.out.println(hibTemplate.find("select a.bs from A a"));
System.out.println(hibTemplate.find("select a.bs from A a"));
}
}
Output is:
[test.DatabaseBugReproduction$B#2942ce]
[null]
It looks like the cache is somehow misconfigured. Where is a mistake and how can I fix it?
Used:
JDK 1.7.0_01 both x32 and x64
Hibernate 3.6.7
Ehcache 2.5.0
Spring 3.1.0
Database: works at least with HSQLDB, H2 and MySQL
After some debugging, I've found that there seems to be a problem with the Query Cache and collection queries. The method that dissembles collections to store in the cache always returns null.
In fact, after googling it up, it turns out that this problems is due to a bug in Hibernate. See the issue description for more information.
While this problem isn't fixed (it seems like it won't) you could re-write your query so you don't need a collection query:
public static void main(String[] args) throws Exception {
HibernateTemplate hibTemplate = new HibernateTemplate(getSessionFactory());
hibTemplate.setCacheQueries(true);
//System.out.println(hibTemplate.find("select a.bs from A a"));
//System.out.println(hibTemplate.find("select a.bs from A a"));
System.out.println(hibTemplate.find("select bs from A a inner join a.bs as bs"));
System.out.println(hibTemplate.find("select bs from A a inner join a.bs as bs"));
}
I've tested it and it works fine.

Hibernate exception handling

I've got a little 'complex' question.
I'm using Hibernate/JPA to make transactions with a DB.
I'm not the DBA, and a client consumes my application, a RESTful web service. My problem is that the DB is altered (not very often, but it still changes). Also, the client does not always respect input for my application (length, type, etc.). When this happens Hibernate throws an exception. The exception is difficult to translate and read from the log, because it has nested exceptions and consists of a lot of text: like I said, very difficult to understand.
I want to know if it's possible to handle exceptions on entity level, throwing maybe a customized exception.
I thank your patience and help in advance.
EDIT:
Fianlly I managed to do what I wanted, not sure if it's done the right way.
App.java
package com.mc;
import org.hibernate.Session;
import com.mc.stock.Stock;
import com.mc.util.HibernateUtil;
import javax.persistence.EntityManager;
public class App {
public static void main(String[] args) {
Set<ConstraintViolation<Stock>> violations;
validator = Validation.buildDefaultValidatorFactory().getValidator();
Scanner scan = new Scanner(System.in);
EntityManager em = null;
System.out.println("Hibernate one to many (Annotation)");
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
Stock stock = new Stock();
String nextLine = scan.nextLine();
stock.setStockCode(nextLine.toString());
nextLine = scan.nextLine();
stock.setStockName(nextLine.toString());
violations = validator.validate(stock);
if (violations.size() > 0) {
StringBuilder excepcion = new StringBuilder();
for (ConstraintViolation<Stock> violation : violations) {
excepcion.append(violation.getMessageTemplate());
excepcion.append("\n");
}
System.out.println(excepcion.toString());
}
session.save(stock);
session.getTransaction().commit();
}
}
FieldMatch.java
package com.mc.constraints;
import com.mc.constraints.impl.FieldMatchValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = FieldMatchValidator.class)
#Documented
public #interface FieldMatch {
String message() default "{constraints.fieldmatch}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String first();
String second();
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Documented
#interface List {
FieldMatch[] value();
}
}
FieldMatchValidator.java
package com.mc.constraints.impl;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import com.mc.constraints.FieldMatch;
import org.apache.commons.beanutils.BeanUtils;
public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> {
private String firstFieldName;
private String secondFieldName;
#Override
public void initialize(final FieldMatch constraintAnnotation) {
firstFieldName = constraintAnnotation.first();
secondFieldName = constraintAnnotation.second();
}
#Override
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
try {
final Object firstObj = BeanUtils.getProperty(value, firstFieldName);
final Object secondObj = BeanUtils.getProperty(value, secondFieldName);
return firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
} catch (final Exception ignore) {
// ignore
}
return true;
}
}
Stock.java
package com.mc.stock;
import com.mc.constraints.FieldMatch;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.validator.constraints.Length;
#Entity
#Table(name = "STOCK")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Stock.findAll", query = "SELECT s FROM Stock s"),
#NamedQuery(name = "Stock.findByStockId", query = "SELECT s FROM Stock s WHERE s.stockId = :stockId"),
#NamedQuery(name = "Stock.findByStockCode", query = "SELECT s FROM Stock s WHERE s.stockCode = :stockCode"),
#NamedQuery(name = "Stock.findByStockName", query = "SELECT s FROM Stock s WHERE s.stockName = :stockName")})
#FieldMatch.List({
#FieldMatch(first = "stockCode", second = "stockName", message = "Code and Stock must have same value")
})
public class Stock implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_stock_id")
#SequenceGenerator(name = "seq_stock_id", sequenceName = "seq_stock_id", initialValue = 1, allocationSize = 1)
#Basic(optional = false)
#Column(name = "STOCK_ID", unique = true, nullable = false)
private Integer stockId;
#Column(name = "STOCK_CODE")
private String stockCode;
#Length(min = 1, max = 20, message = "{wrong stock name length}")
#Column(name = "STOCK_NAME")
private String stockName;
public Stock() {
}
public Stock(Integer stockId) {
this.stockId = stockId;
}
public Integer getStockId() {
return stockId;
}
public void setStockId(Integer stockId) {
this.stockId = stockId;
}
public String getStockCode() {
return stockCode;
}
public void setStockCode(String stockCode) {
this.stockCode = stockCode;
}
public String getStockName() {
return stockName;
}
public void setStockName(String stockName) {
this.stockName = stockName;
}
#Override
public int hashCode() {
int hash = 0;
hash += (stockId != null ? stockId.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Stock)) {
return false;
}
Stock other = (Stock) object;
if ((this.stockId == null && other.stockId != null) || (this.stockId != null && !this.stockId.equals(other.stockId))) {
return false;
}
return true;
}
#Override
public String toString() {
return "com.mc.stock.Stock[ stockId=" + stockId + " ]";
}
}
HibernateUtil.java
package com.mc.util;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
private static final SessionFactory sessionFactory = buildSessionFactory();
private static SessionFactory buildSessionFactory() {
try {
// Create the SessionFactory from hibernate.cfg.xml
return new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
// Make sure you log the exception, as it might be swallowed
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
public static void shutdown() {
// Close caches and connection pools
getSessionFactory().close();
}
}
Oracle DB Structure
CREATE TABLE stock
(
STOCK_ID NUMBER(5) NOT NULL ,
STOCK_CODE VARCHAR2(10) NULL ,
STOCK_NAME VARCHAR2(20) NULL
);
ALTER TABLE stock
add CONSTRAINT PK_STOCK_ID PRIMARY KEY (STOCK_ID);
create sequence seq_stock_id
start with 1
increment by 1
nomaxvalue;
I'm inclined to do as much validation before you get the the DB level. Have a look at Hibernate Validator, http://www.hibernate.org/subprojects/validator.html which is the reference implementation of JSR-303.
Using standard annotations you can enforce constraints and get good error messages before you attempt to put the entities into your database.
I believe this will allow you to validate at the entity level as requested.
I am not sure what you mean about "entity level", but sure. Put a try/catch around the code that is invoking Hibernate. Catch Throwable and rethrow whatever you want. The trick is putting some reason that makes sense in the exception you are throwing.
Of course, one major point is that you should validate all input.
You can implement your own SQLExceptionConverter and handle it the way you want.
Use the property 'hibernate.jdbc.sql_exception_converter' to set your custom converter.
I am unable to find more documentation this, you need to dig into implementations by Hibernate to find more.
By the way, why can't you have a global filter, which catches every exception and decide which exception to re throw as it is or throw a new exception? You will be doing more or less same even if you implement your own SQLExceptionConverter.
according to my experience, you should catch the SQLException, and then u can get easily the SQL error code for specific database.
Eg: your database is mysql and u got error code 1062 . So you can know that error is Duplicated entry error. You can check the mysql error code
http://www.briandunning.com/error-codes/?source=MySQL

Categories

Resources