Hibernate: Insert of Update with EntityManager - java

I have a table with unique constraint on the one field and I want to setup Hibernate EntityManager so it would insert new record only if there is no such record already and will update otherways.
My POJO for the table looks looks this:
#Entity
#Table(name = "links", uniqueConstraints = {
#UniqueConstraint(columnNames = "link", name = "uk_link")
})
public class Link {
private long linkId;
private String link;
private String data;
private String metadata;
private List<Result> results;
#Id
#Column(name="link_id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
public long getLinkId() {
return linkId;
}
public void setLinkId(long linkId) {
this.linkId = linkId;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public String getMetadata() {
return metadata;
}
public void setMetadata(String metadata) {
this.metadata = metadata;
}
#OneToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "link")
public List<Result> getResults() {
return results;
}
public void setResults(List<Result> results) {
this.results = results;
}
#Override
public String toString() {
return "Link [linkId=" + linkId + ", link=" + link + ", data=" + data + ", metadata=" + metadata + "]";
}
public boolean isDataEquals(String data) {
if (this.data == null) {
if (data != null)
return false;
} else if (!this.data.equals(data))
return false;
return true;
}
public boolean isMetadataEquals(String metadata) {
if (this.metadata == null) {
if (metadata != null)
return false;
} else if (!this.metadata.equals(metadata))
return false;
return true;
}
}
And I'm try to resolve the necessary changes through this code:
public Link selectByLink(String link) {
return entityManager
.createQuery("select l from Link l WHERE l.link = :link", Link.class)
.setParameter("link", link)
.getSingleResult();
}
public void insert(Link link) {
this.entityManager.persist(link);
}
public Link update(Link link) {
return this.entityManager.merge(link);
}
public void save(Link link) {
if (link.getLinkId() == 0) {
Link _existing = selectByLink(link.getLink());
if (null != _existing) {
link.setLinkId(_existing.getLinkId());
if (!_existing.isDataEquals(link.getData()) ||
!_existing.isMetadataEquals(link.getMetadata())) {
update(link);
}
} else
insert(link);
}
}
In the Spring log, I see one additional select:
Hibernate: select link0_.link_id as link_id1_0_, link0_.data as data2_0_, link0_.link as link3_0_, link0_.metadata as metadata4_0_ from links link0_ where link0_.link=?
Hibernate: select link0_.link_id as link_id1_0_1_, link0_.data as data2_0_1_, link0_.link as link3_0_1_, link0_.metadata as metadata4_0_1_, results1_.link_id as link_id1_1_3_, results1_.text_id as text_id2_1_3_, results1_.link_id as link_id1_1_0_, results1_.text_id as text_id2_1_0_, results1_.found as found3_1_0_, results1_.level as level4_1_0_ from links link0_ left outer join results results1_ on link0_.link_id=results1_.link_id where link0_.link_id=?
Hibernate: update links set data=?, link=?, metadata=? where link_id=?
I guess, it happens because I use merge function, but if I do not search for the object id prior merging it, the merge will try to insert object instead of updating it. Is there a way to just update the object without testing it first?
And unrelated question, the SQL's looks very messy. all these link0_.link_id as link_id1_0_, can they be suppressed?

The extra join comes as a result of your lazy one to many relationship. Apart from the select for the link itself, an extra select statement is performed to load the collection (that's the root of the famous N+1 problem).
And, for the selection performed before updating, it seems to be the way hibernate persists by default, when entities are detached. If you want to avoid it you should play with its configuration (there's a select-before-update property) or write an update query yourself. You could as well try to avoid Spring detaching Hibernate entities.

Related

JPA Criteria Tuple query fails with missing columns from join in group by clause (Spring Boot 2.7.8 and hibernate 5.6.14.Final)

I am trying to use the JPA Criteria API to filter the results and aggregate them using simple count, min, avg and max. I am using Spring Boot 2.7.8, so I am trying to use Interface-projections such that these aggregated results look the same as the simpler queries done automatically by the Spring repositories.
My domain entity (simplified for brevity) looks like this:
#Entity
#Table(name = "vehicle_stopped")
#IdClass(VehicleStopped.VehicleStoppedPK.class)
public class VehicleStopped implements Serializable {
#Id
#Column(name = "stopped_session_uuid", nullable = false)
private String stoppedSessionUuid;
#Id
#Column(name = "start_ts", nullable = false)
private OffsetDateTime startTs;
#Column(name = "end_ts", nullable = false)
private OffsetDateTime endTs;
#Column(name = "duration_seconds")
private Double durationSeconds;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "zone_id")
private CameraZone cameraZone;
#Override
public VehicleStoppedPK getId() {
VehicleStopped.VehicleStoppedPK pk = new VehicleStopped.VehicleStoppedPK();
pk.setStartTs(this.getStartTs());
pk.setStoppedSessionUuid(this.getStoppedSessionUuid());
return pk;
}
public OffsetDateTime getEndTs() {
return endTs;
}
public void setEndTs(OffsetDateTime endTs) {
this.endTs = endTs;
}
public Double getDurationSeconds() {
return durationSeconds;
}
public void setDurationSeconds(Double durationSeconds) {
this.durationSeconds = durationSeconds;
}
public CameraZone getCameraZone() {
return cameraZone;
}
public void setCameraZone(CameraZone cameraZone) {
this.cameraZone = cameraZone;
}
public VehicleType getVehicleType() {
return vehicleType;
}
public void setVehicleType(VehicleType vehicleType) {
this.vehicleType = vehicleType;
}
public String getStoppedSessionUuid() {
return stoppedSessionUuid;
}
public void setStoppedSessionUuid(String stoppedSessionUuid) {
this.stoppedSessionUuid = stoppedSessionUuid;
}
//some details removed for brevity
#Override
public static class VehicleStoppedPK implements Serializable {
private OffsetDateTime startTs;
private String stoppedSessionUuid;
public VehicleStoppedPK() {
}
public OffsetDateTime getStartTs() {
return startTs;
}
public void setStartTs(OffsetDateTime startTs) {
this.startTs = startTs;
}
public String getStoppedSessionUuid() {
return stoppedSessionUuid;
}
public void setStoppedSessionUuid(String stoppedSessionUuid) {
this.stoppedSessionUuid = stoppedSessionUuid;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
VehicleStoppedPK that = (VehicleStoppedPK) o;
return Objects.equals(startTs, that.startTs) && Objects.equals(stoppedSessionUuid, that.stoppedSessionUuid);
}
#Override
public int hashCode() {
return Objects.hash(startTs, stoppedSessionUuid);
}
#Override
public String toString() {
return "VehicleStoppedPK{" +
"startTs=" + startTs +
", stoppedSessionUuid='" + stoppedSessionUuid + '\'' +
'}';
}
}
}
#Entity
#Table(name = "camera_zone")
public class CameraZone implements Serializable {
#Id
#SequenceGenerator(name = "camera_zone_id_seq", sequenceName = "camera_zone_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "camera_zone_id_seq")
#Column(name = "id", updatable=false)
private Integer id;
#Column(name = "uuid", unique = true)
private String uuid;
#Column(name = "type")
private String type;
#Column(name = "name")
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CameraZone that = (CameraZone) o;
return Objects.equals(id, that.id) && Objects.equals(uuid, that.uuid) && Objects.equals(camera, that.camera) && Objects.equals(type, that.type) && Objects.equals(name, that.name);
}
#Override
public int hashCode() {
return Objects.hash(id, uuid, camera, type, name);
}
}
The code that I have in my Repository implementation looks like this:
public class SpecificationVehicleStoppedRepositoryImpl
implements SpecificationVehicleStoppedRepository {
#Autowired private EntityManager em;
#Autowired ProjectionFactory projectionFactory;
#Override
public List<VehicleStoppedAggregate> getStoppedVehiclesCount(Specification<VehicleStopped> spec) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Tuple> query = builder.createTupleQuery();
Root<VehicleStopped> root = query.from(VehicleStopped.class);
Predicate predicate = spec.toPredicate(root, query, builder);
if (predicate != null) {
query.where(predicate);
}
Path<Number> duration = root.get("durationSeconds");
Path<CameraZone> zone = root.get("cameraZone");
query
.multiselect(zone,
builder.count(root).alias("totalVehicles"),
builder.min(duration).alias("minDuration"),
builder.avg(duration).alias("avgDuration"),
builder.max(duration).alias("maxDuration"))
.groupBy(zone);
List<Tuple> rawResultList = em.createQuery(query).getResultList();
return project(rawResultList, VehicleStoppedAggregate.class);
}
private <P> List<P> project(List<Tuple> results, Class<P> projectionClass) {
return results.stream()
.map(tuple -> {
Map<String, Object> mappedResult = new HashMap<>(tuple.getElements().size());
for (TupleElement<?> element : tuple.getElements()) {
String name = element.getAlias();
mappedResult.put(name, tuple.get(name));
}
return projectionFactory.createProjection(projectionClass, mappedResult);
})
.collect(Collectors.toList());
}
}
The interface-based projection I am trying to populate (using SpelAwareProxyProjectionFactory) is this:
public interface VehicleStoppedAggregate {
CameraZone getCameraZone();
Integer getTotalVehicles();
Double getMinDuration();
Double getAvgDuration();
Double getMaxDuration();
}
The call to getStoppedVehiclesCount() fails with the following error:
ERROR: column "camerazone1_.id" must appear in the GROUP BY clause or be used in an aggregate function
This error is coming from the PostgreSQL database, and rightly so because the SQL hibernate generates is incorrect:
select
vehiclesto0_.zone_id as col_0_0_,
count(*) as col_1_0_,
min(vehiclesto0_.duration_seconds) as col_2_0_,
avg(vehiclesto0_.duration_seconds) as col_3_0_,
max(vehiclesto0_.duration_seconds) as col_4_0_,
camerazone1_.id as id1_2_,
camerazone1_.name as name2_2_,
camerazone1_.type as type3_2_,
camerazone1_.uuid as uuid4_2_
from
vehicle_stopped vehiclesto0_
inner join
camera_zone camerazone1_
on vehiclesto0_.zone_id=camerazone1_.id cross
where
vehiclesto0_.start_ts>=?
and vehiclesto0_.start_ts<=?
and 1=1
and 1=1
and 1=1
group by
vehiclesto0_.zone_id
It is not grouping by the other fields it is requesting from the joined table.
If I had to use a normal class, instead of a Tuple, it would work, but it would mean I would have to create a class with a huge constructor for all fields for Hibernate to populate it.
Somehow, when I use Interface-based projections with Spring's repositories rather than my criteriaquery, the same scenario works. They manage to populate the one-to-many relationships just fine.
Is there a way to fix this and make Hibernate ask for the right fields?
I am using Hibernate 5.6.14.Final (as bundled with Spring Boot 2.7.8).
I believe the "solution" is two create two "independent" query roots and join them together:
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Tuple> query = builder.createTupleQuery();
Root<VehicleStopped> root = query.from(VehicleStopped.class);
// instead of Path<CameraZone> zone = root.get("cameraZone")
Root<CameraZone> zone = query.from(CameraZone.class);
query.where(builder.equal(zone, root.get("cameraZone")));
Path<Number> duration = root.get("durationSeconds");
query
.multiselect(zone,
builder.count(root).alias("totalVehicles"),
builder.min(duration).alias("minDuration"),
builder.avg(duration).alias("avgDuration"),
builder.max(duration).alias("maxDuration"))
.groupBy(zone);
session.createQuery(query).getResultList();
In that case Hibernate 5 produces following SQL (which actually looks weird from my perspective due to missing columns in group by clause):
select
naturalidc1_.id as col_0_0_,
count(*) as col_1_0_,
min(naturalidc0_.duration_seconds) as col_2_0_,
avg(naturalidc0_.duration_seconds) as col_3_0_,
max(naturalidc0_.duration_seconds) as col_4_0_,
naturalidc1_.id as id1_0_,
naturalidc1_.name as name2_0_,
naturalidc1_.type as type3_0_,
naturalidc1_.uuid as uuid4_0_
from
vehicle_stopped naturalidc0_ cross
join
camera_zone naturalidc1_
where
naturalidc1_.id=naturalidc0_.zone_id
group by
naturalidc1_.id
FYI. Your initial query does work in Hibernate 6 and produced SQL does look more correct but still weird:
select
c1_0.id,
c1_0.name,
c1_0.type,
c1_0.uuid,
count(*),
min(v1_0.duration_seconds),
avg(v1_0.duration_seconds),
max(v1_0.duration_seconds)
from
vehicle_stopped v1_0
join
camera_zone c1_0
on c1_0.id=v1_0.zone_id
group by
1,
2,
3,
4

how to delete row in spring boot

I write this in my JpaRepository
#Modifying
#Query("DELETE FROM Backlog b WHERE b.code = ?1")
void deleteBacklog(String code);
and this is my method :
#Transactional
#Override
public Integer deleteBacklog(String code) {
try {
backlogRepository.deleteBacklog(code);
return IReturnedValues.DELETE_BACKLOG_SUCCESS;
}catch(Exception e){
return Integer.MAX_VALUE;
}
}
I got this in the console:
Hibernate: delete from backlog where code=?
but when I check my database I still have the row.
How i call my method:
if(code == null || code == "") {
//user exist, check if the code is valid or not
response=ResponseEntity.ok(IReturnedValues.DELETE_BACKLOG_TOKEN_VALIDE);
}else {
//check if exist a backlog with this code
List<Backlog> backlog = backlogMetier.findByCode(code);
if(backlog == null) {
response= ResponseEntity.ok(IReturnedValues.DELETE_BACKLOG_CODE_NOT_EXISTE);
}else if(backlog != null){
try {
//delete the backlog
retour=backlogMetier.deleteBacklog(code);
response= ResponseEntity.ok(retour);
} catch (Exception e) {
return ResponseEntity.ok(responseClassMax);
}
}
}
this is my entity:
#Entity
public class Backlog {
#Id #GeneratedValue
private Long idBacklog;
private String code;
#ManyToOne(fetch=FetchType.LAZY)
private Utilisateur userBacklog;
private Date creationDate;
private String title;
//getters & setters
//constructors
}
And thanks in advance.
#Query(value=""DELETE FROM Backlog b WHERE b.code = :code")
void deleteBacklog(#Param("code") String code);
It seems Query error, try above code.
use this code
#Repository
public interface BookRepository extends CrudRepository<Book, Long> {
long deleteByTitle(String title);
}
test this method
#Test
#Transactional
public void whenDeleteFromDerivedQuery_thenDeletingShouldBeSuccessful() {
long deletedRecords = repository.deleteByTitle("The Hobbit");
assertThat(deletedRecords).isEqualTo(1);
}

#ManyToMany collections in JPA - foreign key constraint fails - (simulate likes and dislikes buttons)

I'm trying to render like/dislike buttons using JPA and JSF
#Entity
public class APost implements Serializable {
...
#ManyToMany(fetch = FetchType.EAGER)
protected Collection<User> likes;
#ManyToMany(fetch = FetchType.EAGER)
protected Collection<User> dislikes;
#Transient
public Integer getLikesNumber() {
if (likes == null) {
return 0;
}
return likes.size();
}
public Collection<User> getLikes() {
return likes;
}
public void setLikes(Collection<User> likes) {
this.likes = likes;
}
public void addLikes(User user) {
if (likes == null) {
likes = new HashSet<>();
}
likes.add(user);
}
public void removeLikes(User user) {
if (likes != null) {
likes.remove(user);
}
}
#Transient
public Integer getDislikesNumber() {
if (dislikes == null) {
return 0;
}
return dislikes.size();
}
public Collection<User> getDislikes() {
return dislikes;
}
public void setDislikes(Collection<User> dislikes) {
this.dislikes = dislikes;
}
public void addDislikes(User user) {
if (dislikes == null) {
dislikes = new HashSet<>();
}
dislikes.add(user);
}
public void removeDislikes(User user) {
if (dislikes != null) {
dislikes.remove(user);
}
}
}
The User Class :
#Entity
public class User implements Serializable {
...
#Id
private String email;
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "likes")
protected Collection<APost> likes;
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "dislikes")
protected Collection<APost> dislikes;
public Collection<APost> getLikes() {
return likes;
}
public void addLikes(APost post) {
if (likes == null) {
likes = new HashSet<>();
}
likes.add(post);
}
public void removeLikes(APost post) {
if (likes != null) {
likes.remove(post);
}
}
public Collection<APost> getDislikes() {
return dislikes;
}
public void addDislikes(APost post) {
if (dislikes == null) {
dislikes = new HashSet<>();
}
dislikes.add(post);
}
public void removeDislikes(APost post) {
if (dislikes != null) {
dislikes.remove(post);
}
}
#Override
public boolean equals(Object obj) {
if (obj == null)
return false;
User uObj = (User) obj;
return getEmail().equals(uObj.getEmail());
}
}
Facelet : post.xhtml
...
<h:commandLink
action="#{bean.addLike(post.id)}"
<h:graphicImage library="images" name="thumb-up-24x31.png"></h:graphicImage>
</h:commandLink>
...
Bean.java
#ManagedBean
#ConversationScoped
public class OnePostManager implements Serializable {
private static final long serialVersionUID = 1L;
#EJB
private IPostFacade postFacade;
#EJB
private IUserFacade userFacade;
#Inject
private LoginManager loginManager;
...
public String addLike(Long postId) {
if (loginManager.getConnected().equals(false)) {
return "login?redirect=post&&faces-redirect=true";
}
if (postId != null) {
APost post = postFacade.find(postId);
User user = userFacade.find(loginManager.getEmail());
post.addLikes(user);
postFacade.edit(post);
}
return null;
}
}
Now, when I click on the "like" button, I got an exception :
javax.faces.el.EvaluationException: javax.ejb.EJBException: Transaction aborted
...
Caused by: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507- 3faac2b): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`medianovens`.`APOST_USER`, CONSTRAINT `FK_APOST_USER_likes_EMAIL` FOREIGN KEY (`likes_EMAIL`) REFERENCES `USER` (`EMAIL`))
Error Code: 1452
Call: INSERT INTO APOST_USER (dislikes_EMAIL, dislikes_ID) VALUES (?, ?)
bind => [2 parameters bound]
Query: DataModifyQuery(name="dislikes" sql="INSERT INTO APOST_USER (dislikes_EMAIL, dislikes_ID) VALUES (?, ?)")
...
When I look at my database, I've got one table called APOST_USER with 4 columns : dislikes_EMAIL, dislikes_ID, likes_EMAIL, likes_ID
So I suppose that when it tries to add an entry that defines which post a user likes, it tries to fill likes_EMAIL and likes_ID but also expects some values for dislikes_EMAIL and dislikes_ID ...
How can I solve this ?
Note :
When I remove all code concerning dislike functions, the code works OK (My table APOST_USER only has 2 columns, likes_EMAIL and likes_ID) and an entry can be added, but everything goes wrong if I add all code regarding dislike function.
It sounds like your JPA provider is trying to be smart and combine the two references to APost into one. What you should probably do is look into the #JoinTable annotation, which would allow you to definitively specify different tables should be used for your Like and Dislike features.
http://blog.jbaysolutions.com/2012/12/17/jpa-2-relationships-many-to-many/ shows how this is used in practice.
Here's what I think it should look like to fix your mapping problem:
#Entity
public class APost implements Serializable {
...
#JoinTable(name="post_likes")
#ManyToMany(fetch = FetchType.EAGER)
protected Collection<User> likes;
#JoinTable(name="post_dislikes")
#ManyToMany(fetch = FetchType.EAGER)
protected Collection<User> dislikes;
You need to assign both sides of the relationship:
post.addLikes(user);
user.addLikes(post);

Java Lazy Loading with Hibernate/JPA/Spring

I'm having an issue with converting an application to separate REST Server + Web Application rather than having a single application that handles pages + business logic.
I'm having a problem with an org.hibernate.LazyInitializationException being thrown when Jackson is serialising the output.
Below is my JPA configuration and the class in question. I'm aware the lazy initialisation is being caused by the List member, but I don't want this to be populated in the JSON returned. Hence the #JsonIgnore annotation.
PersistenceConfiguration.java
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackageClasses = {ForumRepository.class})
public class PersistenceConfiguration {
#Bean
public EntityManagerFactory entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.mypackage.forum.api.models");
factory.setDataSource(secureDataSource());
factory.afterPropertiesSet();
return factory.getObject();
}
#Bean
public JpaTransactionManager transactionManager() {
EntityManagerFactory factory = entityManagerFactory();
return new JpaTransactionManager(factory);
}
#Bean
public HibernateExceptionTranslator exceptionTranslator() {
return new HibernateExceptionTranslator();
}
#Bean
public DataSource secureDataSource() {
try {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:jboss/datasources/ForumDS");
} catch (Exception e) {
return null;
}
}
}
ForumGroup.java
#Entity
public class ForumGroup {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#JsonIgnore
#OneToMany(mappedBy = "group")
private List<Forum> forums;
private String name;
private String description;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public List<Forum> getForums() {
return forums;
}
public void setForums(List<Forum> forums) {
this.forums = forums;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ForumGroup that = (ForumGroup) o;
if (id != null ? !id.equals(that.id) : that.id != null) return false;
if (name != null ? !name.equals(that.name) : that.name != null) return false;
return true;
}
#Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
#Override
public String toString() {
return "ForumGroup{" +
"id=" + id +
", name='" + name + '\'' +
", description='" + description + '\'' +
'}';
}
}
I presume that I need to configure some sort of Session Manager but not sure the best way to go about doing it in JavaConfig, I've tried various methods via Google with no joy.
Any help would be much appreciated.
Update
I've now managed to resolve this by 1) Adding the #Fetch() annotation to the relevant collections, which means that the collections get populated 2) I have stopped Entities/Models being returned as JSON objects and instead use separate DTOs
org.hibernate.LazyInitializationException happens when call lazy fields outside from session.
In your situation , it's seams you select ForumGroup entities and then, outside from session you trying call entity.getForums(). That's why happening LazyInitializationException.
If you need that collection, then load them eagerly. You can do that by calling:
entity.getForums().size();.Remember, call this inside the session
You can use anotation, to to load collection EAGERLY by default : #ManyToOne(fetch = FetchType.EAGER)
You just need to fetch the ForumGroup along with its forums list.
This can be achieved with a LEFT JOIN FETCH whenever you retrieve the ForumGroup you are interested in:
"from ForumGroup as fg " +
"left join fetch fg.forums "
You can assign a default fetching strategy, but this is a decision that will affect subsequent queries, so make sure you know the trade-offs:
Use a join fetch mode:
#JsonIgnore
#OneToMany(mappedBy = "group")
#Fetch(FetchMode.JOIN)
private List<Forum> forums = new ArrayList<Forum>();
Or a sub-select fetch mode:
#JsonIgnore
#OneToMany(mappedBy = "group")
#Fetch(FetchMode.SUBSELECT)
private List<Forum> forums = new ArrayList<Forum>();
And always initialize your collections, never let them assigned to null.

Play 2.1-Snapshot: Ebean database updates and deletions don't work in Junit test cases

I have a weird problem. I'm using play 2.1-SNAPSHOT with ebeans (=> mysql). I have a very small (test) setup and for some reason database updates and deletions don't work. Items are created in the DB... but updating them does not work.
Here's my bean (which extends a superclass that adds the timestamps (created and modified date)):
AbstractTimestamp (superclass):
#MappedSuperclass
public abstract class AbstractTimestampedBean extends AbstractIdentifiableBean {
/** The date this item has been created. */
#CreatedTimestamp
public Timestamp createdTime;
}
Project Bean (removed unimportant stuff) - hashCode and equals have been created by eclipse - here we overwrite the methods of play.db.ebean.Model:
#Entity
#Table(name = "Projects")
public class Project extends AbstractTimestampedBean {
private static final long serialVersionUID = -6160140283947231026L;
#NotNull
public String title;
#NotNull
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public User owner;
#NotNull
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public User creator;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public Set<User> participants;
#EnumMapping(nameValuePairs = "ACTIVE=A,INACTIVE=I,EXPIRED=E")
public enum Status {
ACTIVE, INACTIVE, EXPIRED
}
public Project() {
}
public Project(final String title, final User creator) {
this.title = title;
this.creator = creator;
this.owner = creator;
}
/*
* (non-Javadoc)
*
* #see play.db.ebean.Model#hashCode()
*/
#Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result
+ (this.creator == null ? 0 : this.creator.hashCode());
result = prime * result
+ (this.owner == null ? 0 : this.owner.hashCode());
result = prime * result
+ (this.participants == null ? 0 : this.participants
.hashCode());
result = prime * result
+ (this.title == null ? 0 : this.title.hashCode());
return result;
}
/*
* (non-Javadoc)
*
* #see play.db.ebean.Model#equals(java.lang.Object)
*/
#Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (this.getClass() != obj.getClass()) {
return false;
}
final Project other = (Project) obj;
if (this.creator == null) {
if (other.creator != null) {
return false;
}
} else if (!this.creator.equals(other.creator)) {
return false;
}
if (this.owner == null) {
if (other.owner != null) {
return false;
}
} else if (!this.owner.equals(other.owner)) {
return false;
}
if (this.participants == null) {
if (other.participants != null) {
return false;
}
} else if (!this.participants.equals(other.participants)) {
return false;
}
if (this.title == null) {
if (other.title != null) {
return false;
}
} else if (!this.title.equals(other.title)) {
return false;
}
return true;
}
Here's the very simple test case:
First run creates a projects - checks that it's there (nothing fails here)
Then we update some stuff - store it - and assert again... and here I can see that the db entries have not been updated.
http://pastebin.com/7zdzWGXw
Here's the superclass that we are using here:
public abstract class AbstractPersistableTestCase {
#Transactional
void saveBean(final Model bean) {
Ebean.save(bean);
}
#Transactional
void deleteBean(final Model bean) {
Ebean.delete(bean);
}
#Transactional
<T extends Model> void deleteBeans(final List<T> beans) {
Ebean.delete(beans);
}
}
Error message from jUnit4:
This is the assertion of the title in the update case => See: db entry has not been updated:
[error] Test test.models.ProjectTest.createAndUpdateProject failed: expected:<'Project_[NEW_]1350681993608'> but was:<Project_[]1350681993608'>
This happens when I try to delete the project:
[error] Test test.models.ProjectTest.deleteProjects failed: Data has changed. updated [0] rows sql[delete from user where id=? and name is null and email is null and created_time is null] bind[null]
Do you guys have an idea why this is happening? I'm really frustrated here...
Regards,
Sascha
It seems to me that you are not adding an Id to your classes.
Try to add this to your superclass:
#MappedSuperclass
public abstract class AbstractModel extends play.db.ebean.Model
{
#Id
public Long id;
public Long getId()
{
return id;
}
// ... here your other attributes
}

Categories

Resources