Associated Entity does not get saved in transaction - java

I am fairly new to entity frameworks, and don't understand why my entity does not get saved correctly.
edit:
I will try to out this test code soon, and see if it works:
#Transactional
public void doConvert(String lakoKod) {
LsZFutelszHavi futelszHavi = new LsZFutelszHavi();
//Give it values like:
futelszHavi.setOrigKorrOssz(new BigDecimal(500));
LsTLako lako = lsTLakoRepository.findOneByLakoKod(lakoKod);
LsZFutelsz futelsz = lsZFutelszRepository.findOneByLsTLako(lako);
//The modification which not get saved into tha Firebird table in the original code. Will it be saved now?
futelsz.setSzezVegenBefOssz(new BigDecimal(100));
futelszHavi.setLsZFutelsz(futelsz);
lsZFutelszHaviRepository.save(futelszHavi);
}
I am trying to convert from an old DBASE database, to a Firebird database. The Firebird database have it's tables mapped by entities. I read a DBASE table, then convert it row by row.
I use #Transactional, to either save all converted entities of the DBASE table, or save none.
I managed to convert every table correctly into the new database except one.
In the other tables, I only had to save one entity per record, and did not have to modify other type of entities. I usually had to create a Type X entity, connect it to a Type Y entity, then save. For save, I use a repository for the entity (an org.springframework.data.repository.PagingAndSortingRepository if it matters)
In that particular DBASE table, I have to create one entity of Type A, connect it to a Type B entity, modify the Type B entity, then save. The problem is, that Type B entity modification does not get saved into the Firebird table. Again, I use a repository to save the entity.
To get the Type B entity, I use it's repository's method:
LsZFutelsz findOneByLsTLako(LsTLako lsTLako);
I guessed, that maybe if I save this Type B entity with it's own repository, it would get modified in the database correctly. It did not help.
Please tell me if additional information is needed.
I copy my slightly altered code here (removed some logging, added some comments). LsZFutelszHavi is the Type A EntityClass, LsZFutelsz is type B EntityClass.
The Konverter abstract class. It is inherited for every DBASE table
public abstract class Konverter<RepositoryType extends CrudRepository<EntityType,Integer>, EntityType> {
protected String dbfPath;
protected DBaseTable sourceTable = null;
protected Logger logger;
protected RepositoryType repository;
protected String dBaseEncoding = DBaseTable.CP852;
public Konverter(String dbfPath, Logger logger, RepositoryType repository) {
this.dbfPath = dbfPath;
this.logger = logger;
this.repository = repository;
}
/*
This method should be called, to start converting
*/
#Transactional
public void konvert() {
try {
/*It loads the DBASE database*/
File sourceFile = new File(fileName);
sourceTable = new DBaseTable(sourceFile, dBaseEncoding);
sourceTable.open(IfNonExistent.ERROR);
Iterator<Record> recordIterator = sourceTable.recordIterator();
int count = 0;
try {
/*Converts the database table row by row*/
count = konvertSorok(recordIterator);
} catch (Exception e) {
throw e;
} finally {
sourceTable.close();
}
}
catch (CorruptedTableException | IOException | RuntimeException e) {
logger.error(QsLoggerUtils.getStackTraceString(e));//e.printStackTrace();
}
}
private int konvertSorok(Iterator<Record> recordIterator) {
int count = 0;
/*Converts the database table row by row*/
while(recordIterator.hasNext())
{
Record record = recordIterator.next();
/* Converting one row */
List<EntityType> entityIterable = konvertToEntity( record );
for (EntityType entityType : entityIterable) {
repository.save(entityType);
}
count++;
}
return count;
}
/**
* This should be implemented in the child method
* #param record
* #return
*/
protected abstract List<EntityType> konvertToEntity(Record record);
}
The child Class, that implmenets konvertToEntity method.
public class Konvert14FutelszHavi extends Konverter<LsZFutelszHaviRepository,LsZFutelszHavi> {
private static Logger logger = LoggerFactory.getLogger(Konvert12Futalany.class);
LsZFutelszHaviRepository lsZFutelszHaviRepository;
LsTLakoRepository lsTLakoRepository;
LsZFutelszRepository lsZFutelszRepository;
LsTEvhoRepository lsTEvhoRepository;
#Autowired
public Konvert14FutelszHavi(LsZFutelszHaviRepository lsZFutelszHaviRepository,
LsTLakoRepository lsTLakoRepository,
LsZFutelszRepository lsZFutelszRepository,
LsTEvhoRepository lsTEvhoRepository) throws IOException {
super(DBaseTable.chkFile(AppKonvertLax.PATH_LSZBF, AppKonvertLax.SOURCE_FILE_FUTELSZ_HAVI), logger, lsZFutelszHaviRepository);
dBaseEncoding = DBaseTable.CP1250;
this.lsTLakoRepository = lsTLakoRepository;
this.lsZFutelszHaviRepository = lsZFutelszHaviRepository;
this.lsZFutelszRepository = lsZFutelszRepository;
this.lsTEvhoRepository = lsTEvhoRepository;
}
#Override
protected List<LsZFutelszHavi> konvertToEntity(Record record) {
String ukod = record.getStringValue("UKOD").substring(1).trim();
BigDecimal ekaptam = new BigDecimal(record.getNumberValue("EKAPTAM").toString());
BigDecimal efutkul = new BigDecimal(record.getNumberValue("EFUTKUL").toString());
ArrayList<LsZFutelszHavi> returnArray = new ArrayList<LsZFutelszHavi>();
LsTLako lsTLako = lsTLakoRepository.findOneByLakoKod(ukod);
LsZFutelsz lsZFutelsz = lsZFutelszRepository.findOneByLsTLako(lsTLako);
if (lsZFutelsz == null) {
return returnArray;
}
/* Here is the modification in the lsZFutelsz (Type B) entity */
lsZFutelsz.setSzezVegenBefOssz(ekaptam);
/* From 10th month to 4th */
for (int i=10; i!=5; i++) {
if (i==13) {
i = 1;
}
String keyNumber = Integer.toString(i);
if (keyNumber.length() == 1) {
keyNumber = "0" + keyNumber;
}
BigDecimal fk = new BigDecimal(record.getNumberValue("FK_"+keyNumber).toString());
LsZFutelszHavi lsZFutelszHavi = new LsZFutelszHavi();
LsTEvho lsTEvho = lsTEvhoRepository.findOneByEvAndHo(2014, i);
lsZFutelszHavi.setLsTEvho(lsTEvho);
lsZFutelszHavi.setFizKorrOssz(fk);
lsZFutelszHavi.setOrigKorrOssz(efutkul);
/* This should be enough to save the lsZFutelsz entity modification I would think */
lsZFutelszHavi.setLsZFutelsz(lsZFutelsz);
returnArray.add(lsZFutelszHavi);
}
/* Even this does not help */
lsZFutelszRepository.save(lsZFutelsz);
return returnArray;
}
}
Repository for the Type B entity
#RepositoryRestResource(collectionResourceRel = LsZFutelszHavi.VERB_FUTELSZ, path = LsZFutelszHavi.VERB_FUTELSZ)
public interface LsZFutelszRepository extends PagingAndSortingRepository<LsZFutelsz, Integer> {
/*-----------------------------------------------------------------------------------------------*/
#RestResource(exported=false)
#Modifying
#Query(value="DELETE FROM ls_z_futelsz f WHERE f.lako_id = ?1", nativeQuery=true)
void deleteByLako(Integer integer);
/*-----------------------------------------------------------------------------------------------*/
LsZFutelsz findOneByLsTLako(LsTLako lsTLako);
}
Repository for the Type A entity
#RepositoryRestResource(collectionResourceRel = LsZFutelsz.VERB_FUTELSZHAVI, path = LsZFutelsz.VERB_FUTELSZHAVI)
public interface LsZFutelszHaviRepository extends PagingAndSortingRepository<LsZFutelszHavi, Integer> {
}
Entity Type A
#Entity
#Table(name="LS_Z_FUTELSZ_HAVI")
#NamedQuery(name="LsZFutelszHavi.findAll", query="SELECT l FROM LsZFutelszHavi l")
public class LsZFutelszHavi extends Audit implements Serializable {
public static final String VERB_FUTELSZ = "futelszamolasok";
/*-----------------------------------------------------------------------------------------------*/
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="GenFutelszHaviID", sequenceName="GEN_LS_Z_FUTELSZ_HAVI_ID", allocationSize= 1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="GenFutelszHaviID")
#Column(name="HAVI_ELSZ_ID")
private Integer haviElszId;
#NotNull
#Column(name="FIZ_KORR_OSSZ")
private BigDecimal fizKorrOssz;
#NotNull
#Column(name="ORIG_KORR_OSSZ")
private BigDecimal origKorrOssz;
//uni-directional many-to-one association to LsFSzlafej
#ManyToOne
#JoinColumn(name="SZLA_ID")
private LsFSzlafej lsFSzlafej;
//uni-directional many-to-one association to LsTEvho
#ManyToOne
#JoinColumns({
#JoinColumn(name="EV", referencedColumnName="EV"),
#JoinColumn(name="HO", referencedColumnName="HO")
})
private LsTEvho lsTEvho;
//bi-directional many-to-one association to LsZFutelsz
#ManyToOne
#JoinColumn(name="ELSZ_ID")
private LsZFutelsz lsZFutelsz;
public LsZFutelszHavi() {
}
//[... setters getters ...]
}
Entity Type B
#Entity
#Table(name="LS_Z_FUTELSZ")
#NamedQuery(name="LsZFutelsz.findAll", query="SELECT l FROM LsZFutelsz l")
public class LsZFutelsz extends Audit implements Serializable {
public static final String VERB_FUTELSZHAVI = "futelszhavi";
/*-----------------------------------------------------------------------------------------------*/
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="GenFutelszID", sequenceName="GEN_LS_Z_FUTELSZ_ID", allocationSize= 1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="GenFutelszID")
#Column(name="ELSZ_ID")
private Integer elszId;
#NotNull
#Column(name="LEOLV_FOGY_GJ")
private BigDecimal leolvFogyGj = BigDecimal.ZERO;
#NotNull
#Column(name="LEOLV_FOGY_OSSZ")
private BigDecimal leolvFogyOssz = BigDecimal.ZERO;
#NotNull
#Column(name="ELOZ_SZEZ_OSSZ")
private BigDecimal elozSzezOssz = BigDecimal.ZERO;
#NotNull
#Column(name="SZEZ_VEGEN_BEF_OSSZ")
private BigDecimal szezVegenBefOssz = BigDecimal.ZERO;
#NotNull
#Column(name="SZOSZT_UTAN_FENNM")
private BigDecimal szosztUtanFennm = BigDecimal.ZERO;
#NotNull
#Column(name="SZOSZTANDO_KULONB")
private BigDecimal szosztandoKulonb = BigDecimal.ZERO;
//uni-directional many-to-one association to LsTLakok
#ManyToOne
#JoinColumn(name="LAKO_ID")
private LsTLako lsTLako;
//bi-directional many-to-one association to LsZFutelszHavi
#OneToMany(mappedBy="lsZFutelsz", cascade={CascadeType.REMOVE})
private List<LsZFutelszHavi> lsZFutelszHaviTetelek;
public LsZFutelsz() {
}
//[... setters getters ...]
}

The code works, The field is just always happen to be the default value.
After testing it with simple methods, the other entity got saved into the database as well. Then I put a breakpoint into the original code which only get breaked, if the new value for the entity is different than the default. The program converted everything without breaking at the breakpoint.
So, I looked up which database I converting, and see what the contents ar. To my surprise it was always zero, the default value.
It's awkward. I was so sure, I misunderstood something, and coded wrong.

Related

How do I add a foreign key to my entity in Room 2.3.0?

I have a project that I have worked with Room 2.2.5, I just updated to version 2.3.0 this is the code of an entity called photo:
#Entity(tableName = Photo.TABLE_NAME, foreignKeys = #ForeignKey(entity = Event.class,
parentColumns = "_id",
childColumns = "id_event_ft",
onDelete = ForeignKey.CASCADE))
public class Photo {
public static final String TABLE_NAME = "Photo";
public static final String COLUMN_ID = BaseColumns._ID;
#PrimaryKey(autoGenerate = true)
#ColumnInfo(index = true, name = COLUMN_ID)
public Long id_photo;
#ColumnInfo(name = "path")
private String path;
#ForeignKey(entity = Event.class,
parentColumns = "_id",
childColumns = "id_event_ft",
onDelete = ForeignKey.CASCADE)
private Long id_event_ft;
public Photo(Long id_photo, String path, Long id_event_ft) {
this.id_photo = id_photo;
this.path = path;
this.id_event_ft = id_event_ft;
}
public Long getId_photo() {
return id_photo;
}
public void setId_photo(Long id_photo) {
this.id_photo = id_photo;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Long getId_event_ft() {
return id_event_ft;
}
public void setId_event_ft(Long id_event_ft) {
this.id_event_ft = id_event_ft;
}
}
Now I am getting the following error when trying to compile
error: annotation type not applicable to this kind of declaration
#ForeignKey(entity = Event.class, parentColumns = "_id", childColumns = "id_event_ft", onDelete = ForeignKey.CASCADE)
^
The error is in the #ForeignKey that is above the variable private Long id_event_ft;
In the documentation I found this:
Added missing target to #ForeignKey annotation preventing its usage outside of the #Entity annotation. (Iced1e)
It is clear that using #ForeignKey outside of the #Entity annotation is no longer allowed, but then how do I bind the id_event_ft variable to the foreign key?, How do I assign a value to it now?
I hope someone can help me, thank you very much
Using ForeignKey does not automatically (magically) make a relationship. Rather it allows a relationship to be supported primarily by enforcing referential integrity.
There is no need for a ForeignKey definition for a Foreign Key (relationship) to exist.
That is it is defining a rule that says that the value of the child column (id_event_ft) MUST be a value that is present in the parent column (_id). It also supports handling if there is a Foreign Key Conflict (e.g. onDelete as you have used).
Actually providing a suitable value is something that you have to do programmatically, that is id adding a photo you have to determine which Event the photo is to be linked/related to.
You can use #Relation to simplify extracting related data.
So consider the following:
An Event Entity (nice and simple for demonstration)
#Entity
public class Event {
#PrimaryKey(autoGenerate = true)
Long _id = null;
String other_columns;
public Event(){}
#Ignore
public Event(String other_columns) {
this.other_columns = other_columns;
}
}
A Photo's parent column will be the _id column.
Second Ignored (i.e. Ignored by Room) constructor otherwise Room issue a warning like *warning: There are multiple good constructors and Room will pick the no-arg constructor. *
A slightly changed Photo Entity :-
#Entity(tableName = Photo.TABLE_NAME,
foreignKeys = #ForeignKey(
entity = Event.class,
parentColumns = "_id",
childColumns = "id_event_ft",
onDelete = ForeignKey.CASCADE),
indices = #Index("id_event_ft") //<<<<<<<<<< ADDED as Room warns if omitted
)
public class Photo {
public static final String TABLE_NAME = "Photo";
public static final String COLUMN_ID = BaseColumns._ID;
#PrimaryKey(autoGenerate = true)
#ColumnInfo(index = true, name = COLUMN_ID)
public Long id_photo;
#ColumnInfo(name = "path")
private String path;
/* <<<<<<<< COMMENTED OUT >>>>>>>>>>
#ForeignKey(entity = Event.class,
parentColumns = "_id",
childColumns = "id_event_ft",
onDelete = ForeignKey.CASCADE)
*/
private Long id_event_ft;
public Photo(Long id_photo, String path, Long id_event_ft) {
this.id_photo = id_photo;
this.path = path;
this.id_event_ft = id_event_ft;
}
public Long getId_photo() {
return id_photo;
}
public void setId_photo(Long id_photo) {
this.id_photo = id_photo;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Long getId_event_ft() {
return id_event_ft;
}
public void setId_event_ft(Long id_event_ft) {
this.id_event_ft = id_event_ft;
}
}
For demonstration of retrieving via a relationship the POJO EventWithPhotos :-
public class EventWithPhotos {
#Embedded
Event event;
#Relation(entity = Photo.class,parentColumn = "_id",entityColumn = "id_event_ft")
List<Photo> photos;
}
Now a Dao AllDao:-
#Dao
interface AllDao {
#Insert
long insert(Event event);
#Insert
long insert(Photo photo);
#Transaction
#Query("SELECT * FROM event")
List<EventWithPhotos> getAllEventsWithPhotos();
}
How do I assign a value to it now?
Now an example that puts it all together adding 2 Events the first with 2 photos, the second with 1 photo. Note the different techniques used:-
dao = db.getAllDao();
// Prepare to add an Event
Event newEvent = new Event();
newEvent.other_columns = "Event1";
// Add the Event retrieving the id (_id column)
long eventId = dao.insert(newEvent);
// Prepare a photo to be added to Event1
Photo newPhoto = new Photo(null,"photo1",eventId);
// Add the Photo to Event1
long photoid = dao.insert(newPhoto);
// Add second photo to Event 1 using the 2nd constructor
dao.insert(new Photo(null,"photo2",eventId));
// Add Event2 with a photo all in a single line (again using the 2nd constrcutor)
long event2Id;
dao.insert(new Photo(null,"photo3",event2Id = dao.insert(new Event("Event2"))));
// Get and output Each Event with the Photos for that Event
List<EventWithPhotos> allEventsWithPhotosList = dao.getAllEventsWithPhotos();
for (EventWithPhotos ewp: allEventsWithPhotosList) {
Log.d("EVENTPHOTOINFO","Event is " + ewp.event.other_columns);
for (Photo p: ewp.photos) {
Log.d("EVENTWITHPHOTO","\tPhoto is " + p.getPath() + " ID is " + p.getId_photo());
}
}
Result
When run the log contains :-
D/EVENTPHOTOINFO: Event is Event1
D/EVENTWITHPHOTO: Photo is photo1 ID is 1
D/EVENTWITHPHOTO: Photo is photo2 ID is 2
D/EVENTPHOTOINFO: Event is Event2
D/EVENTWITHPHOTO: Photo is photo3 ID is 3
The Database (view with Database Inspector) shows:-
The Event table :-
The Photo table :-
Relations
#Many To One
if the relation is many photo for one event, use ORM Hibernate, this is the easiest way to define foreign key with constraint on your Photo Entity
#ManyToOne
#JoinColumn(name = "id", nullable = false)
private Event id_event_ft;

Spring + MySQL records being duplicated instead of updated

In my project I'm migrating data from SQLite db into MySQL db. The problem is that when I migrate multiple times, the already existing records are being duplicated, instead of being updated(in case there are any changes in SQLite, otherwise no action is expected).
I'm using JpaRepository.saveAll(), which internally invokes save() for each entity:
#Transactional
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
if (this.entityInformation.isNew(entity)) {
this.em.persist(entity);
return entity;
} else {
return this.em.merge(entity);
}
}
Then isNew() is invoked but I'm not sure how it compares records in the target db to the ones that are currently being migrated and if that's where the problem occurs at all.
Do you have any suggestions to solving my problem?
That's my entity:
#Entity
#Table(name = "movies")
public class Movie {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
-- properties
-- constructors/getters/setters/equals/hashcode
}
That's the service that migrates movies(SQLiteMovie is the entity extracted from SQLite, which is then mapped to (MySQL)Movie):
#Service
public class MovieMigrationServiceImpl implements MovieMigrationService {
private static final int MOVIE_TYPE = 1;
private final MovieMapper movieMapper;
private final MovieService movieService;
private final SQLiteMovieService sqliteMovieService;
public MovieMigrationServiceImpl(MovieMapper movieMapper, MovieService movieService,
SQLiteMovieService sqliteMovieService) {
this.movieMapper = movieMapper;
this.movieService = movieService;
this.sqliteMovieService = sqliteMovieService;
}
#Override
public String migrateMovies() {
Collection<SQLiteMovie> sqliteMovies = sqliteMovieService.getAllByMetadataType(MOVIE_TYPE);
Collection<Movie> movies = mapMovies(sqliteMovies);
movieService.saveMovies(movies);
return movies.size() + " movies migrated successfully!";
}
private Collection<Movie> mapMovies(Collection<SQLiteMovie> sqliteMovies) {
return sqliteMovies.stream()
.map(movieMapper::toMovie)
.collect(Collectors.toList());
}
}
And finally that's the structure of the table in MySQL:
Thanks in advance!

Getting entity from table without having primary key in Hibernate

I'm currently working on a project where I'm trying to get a list of enities from table which does not have a primary key (dk_systemtherapie_merkmale). This table is 1:n related to another table (dk_systemtherapie). See the screenshot for the table structure.
When getting an entry for dk_systemtherapie, the program fetches the Collection "dkSystemtherapieMerkmalesById". However, the first table entry is fetched as often as the number of actual entries in the table is. It never fetches the other entries from dk_systemtherapie_merkmale. I assume it has something to do with the fact that hibernate can't differ between the entries, but I don't know how to fix it.
Table schema
I've created two corresponding entity classes, dk_systemtherapie:
#Entity
#Table(name = "dk_systemtherapie", schema = "***", catalog = "")
public class DkSystemtherapieEntity {
private int id;
private Collection<DkSystemtherapieMerkmaleEntity> dkSystemtherapieMerkmalesById;
#Id
#Column(name = "id")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#OneToMany(mappedBy = "dkSystemtherapieByEintragId")
public Collection<DkSystemtherapieMerkmaleEntity> getDkSystemtherapieMerkmalesById() {
return dkSystemtherapieMerkmalesById;
}
public void setDkSystemtherapieMerkmalesById(Collection<DkSystemtherapieMerkmaleEntity> dkSystemtherapieMerkmalesById) {
this.dkSystemtherapieMerkmalesById = dkSystemtherapieMerkmalesById;
}
}
Here the second one, which is accessing the table without a primary key, dk_systhemtherapie_merkmale:
#Entity #IdClass(DkSystemtherapieMerkmaleEntity.class)
#Table(name = "dk_systemtherapie_merkmale", schema = "***", catalog = "")
public class DkSystemtherapieMerkmaleEntity implements Serializable {
#Id private Integer eintragId;
#Id private String feldname;
#Id private String feldwert;
private DkSystemtherapieEntity dkSystemtherapieByEintragId;
#Basic
#Column(name = "eintrag_id")
public Integer getEintragId() {
return eintragId;
}
public void setEintragId(Integer eintragId) {
this.eintragId = eintragId;
}
#Basic
#Column(name = "feldname")
public String getFeldname() {
return feldname;
}
public void setFeldname(String feldname) {
this.feldname = feldname;
}
#Basic
#Column(name = "feldwert")
public String getFeldwert() {
return feldwert;
}
public void setFeldwert(String feldwert) {
this.feldwert = feldwert;
}
#Id
#ManyToOne
#JoinColumn(name = "eintrag_id", referencedColumnName = "id")
public DkSystemtherapieEntity getDkSystemtherapieByEintragId() {
return dkSystemtherapieByEintragId;
}
public void setDkSystemtherapieByEintragId(DkSystemtherapieEntity dkSystemtherapieByEintragId) {
this.dkSystemtherapieByEintragId = dkSystemtherapieByEintragId;
}
}
I assume the problem is releated to the fact that Hibernate is using the following annotation as the one and only id for fetching data from database.
#Id
#ManyToOne
#JoinColumn(name = "eintrag_id", referencedColumnName = "id")
public DkSystemtherapieEntity getDkSystemtherapieByEintragId() {
return dkSystemtherapieByEintragId;
}
This leads to the problem that when getting more than one entry with the same id (as the id is not unique), you will get the number of entries you would like to but hibernate is always fetching the first entry for this id. So in fact you are getting dublicate entries.
So how to fix this?
According to this question: Hibernate and no PK, there are two workarounds which are actually only working when you don't have NULL entries in your table (otherwise the returning object will be NULL as well) and no 1:n relationship. For my understanding, hibernate is not supporting entities on tables without primary key (documentation). To make sure getting the correct results, I would suggest using NativeQuery.
Remove the Annotations and private DkSystemtherapieEntity dkSystemtherapieByEintragId; (incl. beans) from DkSystemtherapieMerkmaleEntity.java und add a constructor.
public class DkSystemtherapieMerkmaleEntity {
private Integer eintragId;
private String feldname;
private String feldwert;
public DkSystemtherapieMerkmaleEntity(Integer eintragId, String feldname, String feldwert) {
this.eintragId = eintragId;
this.feldname = feldname;
this.feldwert = feldwert;
}
public Integer getEintragId() {
return eintragId;
}
public void setEintragId(Integer eintragId) {
this.eintragId = eintragId;
}
public String getFeldname() {
return feldname;
}
public void setFeldname(String feldname) {
this.feldname = feldname;
}
public String getFeldwert() {
return feldwert;
}
public void setFeldwert(String feldwert) {
this.feldwert = feldwert;
}
}
Remove private Collection<DkSystemtherapieMerkmaleEntity> dkSystemtherapieMerkmalesById; (incl. beans) from DkSystemtherapieEntity.java.
Always when you need to get entries for a particular eintrag_id, use the following method instead of the Collection in DkSystemtherapieEntity.java.
public List<DkSystemtherapieMerkmaleEntity> getDkSystemtherapieMerkmaleEntities(int id) {
Transaction tx = session.beginTransaction();
String sql = "SELECT * FROM dk_systemtherapie_merkmale WHERE eintrag_id =:id";
List<Object[]> resultList;
resultList = session.createNativeQuery(sql)
.addScalar("eintrag_id", IntegerType.INSTANCE)
.addScalar("feldname", StringType.INSTANCE)
.addScalar("feldwert", StringType.INSTANCE)
.setParameter("id", id).getResultList();
tx.commit();
List<DkSystemtherapieMerkmaleEntity> merkmale = new ArrayList<>();
for (Object[] o : resultList) {
merkmale.add(new DkSystemtherapieMerkmaleEntity((Integer) o[0], (String) o[1], (String) o[2]));
}
return merkmale;
}
Call getDkSystemtherapieMerkmaleEntities(dkSystemtherapieEntityObject.getid()) instead of getDkSystemtherapieMerkmalesById().

Hibernate #CollectionOfElements and #Parent

I've done the following mapping:
#Entity
#Table(name = "NWS_NEWS")
public class News implements Serializable {
private static final long serialVersionUID = 5246618151933389186L;
private String id;
private List<Picture> pictures;
+ OTHER fields / getters / setters, no matter
#Id
#GeneratedValue(generator = "juuid")
#Column(length = 36)
public String getId() {
return id;
}
#CollectionOfElements
#JoinTable(name = "NWS_PICTURES",joinColumns = #JoinColumn(name="NEWS_ID"))
#CollectionId(
columns= #Column(name="PICTURE_ID"),
type=#Type(type="long"),
generator="sequence")
public List<Picture> getPictures() {
return pictures;
}
public void setPictures(List<Picture> pictures) {
this.pictures = pictures;
}
}
And my picture is:
#Embeddable
public class Picture implements Serializable {
private static final long serialVersionUID = -1397366206984323622L;
private News news;
private String path;
private ImageSize imageSize;
#Parent
public News getNews() {
return this.news;
}
#Column(name = "path", nullable=false)
public String getPath() {
return path;
}
#Enumerated(EnumType.STRING)
#Column(name = "size", nullable=false)
public ImageSize getImageSize() {
return imageSize;
}
public void setImageSize(ImageSize imageSize) {
this.imageSize = imageSize;
}
public void setNews(News news) {
this.news = news;
}
public void setPath(String path) {
this.path = path;
}
}
And my dao test is:
#Test
public void testAddPicturesToNews() {
News newsToSave = new News();
// Create big picture
Picture pBig = new Picture();
pBig.setImageSize(ImageSize.BIG);
pBig.setPath("/tmp/blabla_big.jpg");
// Create medium picture
Picture pMedium = new Picture();
pMedium.setImageSize(ImageSize.MEDIUM);
pMedium.setPath("/tmp/blabla_med.jpg");
// Set the pictures in the news
List<Picture> picturesList = new ArrayList<Picture>();
picturesList.add(pBig);
picturesList.add(pMedium);
newsToSave.setPictures(picturesList);
// Save the news
this.newsDAO.saveOrUpdate(newsToSave);
String newsId = newsToSave.getId();
News newsLoaded = this.newsDAO.findById(newsId);
List<Picture> picturesLoaded = newsLoaded.getPictures();
for ( Picture pictureLoaded : picturesLoaded ) {
System.out.println(pictureLoaded.getPath());
System.out.println(pictureLoaded.getImageSize());
System.out.println(pictureLoaded.getNews());
System.out.println("\n");
}
}
But the output is:
/tmp/blabla_big.jpg
BIG
null
/tmp/blabla_med.jpg
MEDIUM
null
Actually i don't understand why getNews() returns null in the child entity entity, while it has the "#Parent" annotation. Am i doing something wrong?
Anyway the concept of getting the parent in a child entity seems a bit strange for me since what would happen if i do something like that:
News news1 = new News();
News news2 = new News();
List<Picture> picList = new ArrayList<Picture>();
Picture picture1 = new Picture();
picturesList.add(picture1);
picture1.setNews(news2);
news1.setPictures(picList);
this.newsDAO.saveOrUpdate(news1);
this.newsDAO.saveOrUpdate(news2);
What would happen since the same picture will be in news1 list, but also its parent was set to news2???
I think i'll do without that parent, i don't need that so much but it's just curiosity...
Thanks
Btw i'd like to have only one picture of each size for a news -> there can't be 2 small pictures for the same news.
So is it possible to add a unique constraint on {news_id , imageSize} in my embedded entity? I don't see how to do that since no id field is declared in my Picture embeddable entity
I'm not familiar with the #Parent annotation for an #Embeddable, but for "real" relationships it's always recommended to do something like this:
// News class
public void setPictures(List<Picture> pictures) {
this.pictures = pictures;
for (Picture picture : pictures) {
picture.setNews(this);
}
}
public void addPicture(Picture picture) {
this.pictures.add(picture);
picture.setNews(this);
}
Remember that OOP, as opposed to the relational model, has only the notion of a "one-way" navigation, and that you should build the "two-way" by yourself. Encapsulating this behavior in the setter makes this transparent to your consumers. So, I'm not sure why your #Parent is not working, but I would try to do the opposite:
// what you have:
newsToSave.setPictures(picturesList);
// what I'd try:
pMedium.setNews(newsToSave);
What would happen since the same picture will be in news1 list, but also its parent was set to news2???
Well, an #Embeddable is an object which is "embedded" into another, meaning that it belongs only to that other object (parent). As such, it should contain only one parent. If you change the parent, it'll belong only to this new parent. If you need an object (Picture) to have a relationship with other objects (News), you'll need a #ManyToMany (if the other object, News, may also be linked to several Picture)

Java JPA "Error compiling the query" when it uses an enum

The following JPA query doesn't compile:
#NamedQuery(name = "PSA.findBySourceSystem",
query = "SELECT p FROM PSA p WHERE p.sourceSystem.id = :sourceSystemId")
p.sourceSystem is the following enum:
public enum SourceSystem {
FIRST(3, "ABC"), SECOND(9, "DEF"), THIRD(17, "GHI");
private int id;
private String code;
...
}
and is mapped in PSA's base class:
public class PsaBase implements Serializable {
#Column(name = "sourceSystemId")
#Enumerated(EnumType.ORDINAL)
protected SourceSystem sourceSystem;
...
}
The query compiles and runs fine if I replace p.sourceSystem.id in the query with something more benign.
Thank you in advance for any help.
It shouldn't compile.
You have to resolve the required enum value manually before passing it as a query parameter:
#NamedQuery(name = "PSA.findBySourceSystem",
query = "SELECT p FROM PSA p WHERE p.sourceSystem = :sourceSystem")
.
public enum SourceSystem {
...
private static Map<Integer, SourceSystem> valuesById = new HashMap<Integer, SourceSystem>();
static {
for (SourceSystem s: values())
valuesById.put(s.id, s);
}
public static SourceSystem findById(int id) {
return valuesById.get(id);
}
}
.
em.createNamedQuery("PSA.findBySourceSystem")
.setParameter("sourceSystem", SourceSystem.findById(sourceSystemId));
EDIT:
Since sourceSystem is annotated as #Enumerated(EnumType.ORDINAL), it's stored in the database as the ordinal numbers of the corresponding enum values, therefore FIRST is stored as 0. JPA doesn't directly support using arbitrary field of the enum value to identify it in the database. If your database schema assumes so, you can do the following trick to decouple state of your object from the database schema:
public class PsaBase implements Serializable {
protected SourceSystem sourceSystem;
#Column(name = "sourceSystemId")
public Integer getSourceSystemId() {
return sourceSystem.getId();
}
public void setSourceSystemId(Integer id) {
this.sourceSystem = SourceSystem.findById(id);
}
... getter and setter of sourceSystem with #Transient ...
}

Categories

Resources