I am new to Spring Data JPA and CRUD repositories. I have code that can save to the database if I save each individual entity, but thought that I should be able to save the parent entity and have the contained child entities automatically get saved or updated. Am I doing something wrong?
The code that executes the save:
private static CustomerRepository customerRepository;
#Transactional
public ResultCreateCustomer addCustomer(NameTable nameIn, Address addressIn)
throws Exception {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
customerRepository = context.getBean(CustomerRepository.class);
ResultCreateCustomer result = new ResultCreateCustomer(0, 0, 0, DATABASE_OR_SYSTEM_ERROR);
try {
NameTable theName = new NameTable();
theName.setFirstName(nameIn.getFirstName());
theName.setLastName(nameIn.getLastName());
Address theAddress = new Address();
theAddress.setStreetNo(addressIn.getStreetNo());
theAddress.setStreetName(addressIn.getStreetName());
theAddress.setCityStateZip(addressIn.getCityStateZip());
Customer theCustomer = new Customer();
theCustomer.setNameTable(theName);
theCustomer.setAddress(theAddress);
customerRepository.save(theCustomer);
} catch (Exception e) {
this.log.error("got exception: " + e.getClass() + ": " + e.getMessage());
}
context.close();
return result;
}
The code for the Customer entity:
#Entity
#org.hibernate.annotations.Proxy(lazy=false)
#Table(name="Customer")
public class Customer implements Serializable {
public Customer() {
}
#Column(name="ID", nullable=false, unique=true)
#Id
#GeneratedValue(generator="COM_COMPORIUM_CUSTOMER_DOMAIN_CUSTOMER_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name="COM_COMPORIUM_CUSTOMER_DOMAIN_CUSTOMER_ID_GENERATOR", strategy="native")
private int ID;
#ManyToOne(targetEntity=com.comporium.customer.addresses.Address.class, fetch=FetchType.LAZY)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="AddressID", referencedColumnName="ID") })
private com.comporium.customer.addresses.Address address;
#OneToOne(targetEntity=com.comporium.customer.contactrolodex.NameTable.class, fetch=FetchType.LAZY)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="NameTableID", nullable=false) })
private com.comporium.customer.contactrolodex.NameTable nameTable;
#Column(name="IndustryCode", nullable=false)
private int industryCode;
#Column(name="DemographicCode", nullable=false)
private int demographicCode;
#Column(name="Ranking", nullable=false)
private int ranking;
public void setNameTable(com.comporium.customer.contactrolodex.NameTable value) {
this.nameTable = value;
}
public com.comporium.customer.contactrolodex.NameTable getNameTable() {
return nameTable;
}
// Other setters and getters follow
}
The code for the first child class:
#Entity
#org.hibernate.annotations.Proxy(lazy=false)
#Table(name="NameTable")
public class NameTable implements Serializable {
public NameTable() {
}
#Column(name="ID", nullable=false, unique=true)
#Id
#GeneratedValue(generator="COM_COMPORIUM_CUSTOMER_CONTACTROLODEX_NAMETABLE_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name="COM_COMPORIUM_CUSTOMER_CONTACTROLODEX_NAMETABLE_ID_GENERATOR", strategy="native")
private int ID;
#Column(name="FirstName", nullable=true, length=30)
private String firstName;
#Column(name="LastName", nullable=true, length=40)
private String lastName;
#OneToOne(mappedBy="nameTable", targetEntity=com.comporium.customer.domain.Customer.class, fetch=FetchType.LAZY)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.LOCK})
private com.comporium.customer.domain.Customer customer;
}
The repository interface, CustomerRepository:
package com.comporium.customer.repositories;
import org.springframework.data.repository.CrudRepository;
import com.comporium.customer.domain.Customer;
public interface CustomerRepository extends CrudRepository<Customer, Integer> {
}
When I run the executable (via a SOAP call), I get an exception:
2014-11-19 09:57:59,253 ERROR VPcodeDao:306 - got exception: class org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation: com.comporium.customer.domain.Customer.nameTable -> com.comporium.customer.contactrolodex.NameTable; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation: com.comporium.customer.domain.Customer.nameTable -> com.comporium.customer.contactrolodex.NameTable
Is there a way for the save(theCustomer) to save the contained child entities also?
not sure if you ever get a solution for this. but the solution is to define cascading
#OneToOne(cascade = {CascadeType.ALL}, mappedBy="nameTable", targetEntity=com.comporium.customer.domain.Customer.class, fetch=FetchType.LAZY)
Related
I'm using hibernate with manyToMany relation and I want to display data from database
Thank you in advance.
I get this errors:
database :
Here is the code :
Class EnseignerId :
#Embeddable
public class EnseignerId implements Serializable {
//id professeur
#Column(name="professeur_code")
private int code;
//id matiere
#Column(name="matiere_reference")
private String reference;
public EnseignerId() {
super();
}
//getters and setters...
Class Enseigner :
#Entity
#Table(name="Enseigner")
public class Enseigner {
#EmbeddedId
private EnseignerId id = new EnseignerId();
//id prof
#ManyToOne
#MapsId("code")
private Professeur professeur;
//id matiere
#ManyToOne
#MapsId("reference")
private Matiere matiere;
#Column(name="heures")
private int heures;
//constructor getters and setters...
Class Professeur:
#Entity
#Table(name="professeur")
public class Professeur {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="code")
private int code ;
#Column(name="nom")
private String nom;
#Column(name="prenom")
private String prenom;
...
#OneToMany(
mappedBy="professeur",
cascade = CascadeType.ALL,
orphanRemoval = true)
private List<Enseigner> matieres; //List<Association> Class; //I followed a tutorial
//constructor getters and setters...
public List<Enseigner> getMatieres() {
return matieres;
}
Class Matiere :
#Entity
#Table(name="matiere")
public class Matiere {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="reference")
private String reference;
#Column(name="description")
String description;
#Column(name="volume")
int volume;
#OneToMany(
mappedBy= "matiere",
cascade = CascadeType.ALL,
orphanRemoval = true)
private List<Enseigner> professeurs;
//constructor getters and setters...
getProfesseur() method :
public Professeur getProfesseur(int code) {
SessionFactory sessionFactory = getSessionFactory(); //static method
Session session = sessionFactory.openSession();
Professeur professeur = null;
try {
session.getTransaction().begin();
System.out.println("------------Calling getProfesseur()----------");
professeur = session.get(Professeur.class, code);
if(professeur != null) {
System.out.println(professeur);
}else {
throw new DAOException( "CODE INVALIDE!" );
}
}
catch(Exception e ) {
System.out.println(e.getMessage());
}
finally {
session.close();
}
return professeur;
}
Saving data and getting professors who don't have an Matiere work. but getting Matiere or professeur whose primary key exists in the join table Enseigner generate errors when I do something like :
Professeur prof =profDAO.getProfesseur(2); //*generates errors* //the professor with id=2 exists in database
System.out.println(prof);
List<Enseigner> enseigner = prof.getMatieres(); //*generates errors*...
List<Matiere> matieres = new ArrayList<>();
for(Enseigner ens : enseigner) {
matieres.add(ens.getMatiere());
System.out.println(ens);
}
/*for(Matiere mat : matieres) {
System.out.println(mat);
}*/
This problem has nothing to do with Hibernate. Please inspect the stack trace carefully: your Enseigner.toString() calls Professeur.toString() which in turn calls Enseigner.toString() again and so on.
I notice this problem more and more these days when people blindly use Lombok with its #Data (which should almost never be used), #ToString and #EqualsAndHashCode. These generate respective methods that include all fields!
You need to remove these annotations or set them up so that they use only the fields that you really need. Most of the time your equals() and hashCode() are not needed when you write web apps with ORM. Hibernate ensures you don't have 2 instances of the same entity.
On the other hand toString() can be useful, but we shouldn't include all fields in it - just the ones that are helpful in identifying the entity.
You have cyclic reference. You need exclude field professeurs and matieres by #JsonIgnoreProperties
I have added Hibernate filters on my entities . These filters are applied on queries which fetch Collection of entity but not applied on queries which fetch single entity. Below is my code.
AOrganization.java
#MappedSuperclass
#FilterDef(name = "OrgFilter", parameters = { #ParamDef(name = "allowedOrgIdList", type = "long") })
#Filter(name = "OrgFilter", condition = "org_id in (:allowedOrgIdList)")
public class AOrganization implements Serializable {
#ManyToOne()
#JoinColumn(name = "org_id", nullable = true)
private Organization organization;
public Organization getOrganization() {
return organization;
}
public void setOrganization(Organization organization) {
this.organization = organization;
}
}
Site.java
#Data
#Entity
#Table(name = "site")
public class Site extends AOrganization{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name = "site_name")
private String siteName;
#Override
public String toString() {
return "Site [id=" + id + ", siteName=" + siteName + "]";
}
}
SiteService.java
public interface SiteService {
public List<Site> getAllSites();
public List<Site> getSiteBySiteName(String siteName);
public Site updateSiteName(Long id, String siteName);
}
SiteRepository.java
#Repository
public interface SiteRepository extends AOrganizationRepository<Site, Long> {
public List<Site> findBySiteName(String siteName);
public List<Site> findByOrganization_Id(Long orgId);
}
AOrganizationRepository.java
#NoRepositoryBean
public interface AOrganizationRepository<T, ID extends java.io.Serializable> extends CrudRepository<T, ID> {
}
SiteServiceImpl.java
#Service
public class SiteServiceImpl implements SiteService {
#Autowired
private EntityManager entityManager;
#Autowired
private SiteRepository siteRepository;
#Override
public List<Site> getAllSites() {
Iterable<Site> sites = siteRepository.findAll();
List<Site> allSites = new ArrayList<>();
sites.forEach(allSites::add);
return allSites;
}
#Override
public List<Site> getSiteBySiteName(String siteName) {
List<Site> allSites = siteRepository.findBySiteName(siteName);
return allSites;
}
#Override
public Site updateSiteName(Long id,String siteName) {
Site site = siteRepository.findById(id).get();
if(site == null)
return null;
site.setSiteName(siteName);
siteRepository.save(site);
return site;
}
}
AOrganizationAspect.java
#Aspect
#Component
#Slf4j
public class AOrganizationAspect {
#PersistenceContext
private EntityManager entityManager;
#Pointcut("execution(public * com.harshal.springboot.springfilter.repository.AOrganizationRepository+.*(..))")
protected void aOrganizationRepositoryRepositoryMethod() {
log.info("aOrganizationRepositoryRepositoryMethod");
}
#Around(value = "aOrganizationRepositoryRepositoryMethod()")
public Object enableOwnerFilter(ProceedingJoinPoint joinPoint) throws Throwable {
// Variable holding the session
Session session = null;
try {
// Get the Session from the entityManager in current persistence context
session = entityManager.unwrap(Session.class);
// Enable the filter
Filter filter = session.enableFilter("OrgFilter");
// Set the parameter from the session
List<Long> orgList = getAllowedOrgIdList();
filter.setParameterList("allowedOrgIdList", orgList);
} catch (Exception ex) {
// Log the error
log.error("Error enabling OrgFilter : Reason -" + ex.getMessage());
}
// Proceed with the joint point
Object obj = joinPoint.proceed();
// If session was available
if (session != null) {
// Disable the filter
session.disableFilter("OrgFilter");
}
// Return
return obj;
}
private List<Long> getAllowedOrgIdList() {
return Arrays.asList(2l);
}
}
So , hibernate filters are applied if method getSiteBySiteName is called and filters are not applied if findById method is called.
Below are queries :
For getSiteBySiteName :
select site0_.id as id1_2_, site0_.org_id as org_id3_2_,
site0_.site_name as site_nam2_2_ from site site0_ where site0_.org_id
in (?) and site0_.site_name=?
Please help . Thanks in advance.
For findById
select site0_.id as id1_2_0_, site0_.org_id as org_id3_2_0_,
site0_.site_name as site_nam2_2_0_, organizati1_.id as id1_1_1_,
organizati1_.address as address2_1_1_, organizati1_.org_name as
org_name3_1_1_ from site site0_ left outer join organization
organizati1_ on site0_.org_id=organizati1_.id where site0_.id=?
findById is using the EntityManager.find method and do not create a query.
Plus Hibernate Filters only work on queries.
You should write a query instead of using findById
I am trying to have a bi-directional relationship between the child(ZonePoint_DTO) and parent(ZoneDTO), where the parent has a composite key.
The current mapping is resulting in nulls being entered in the child(ZonePoint_DTO) table when I try to enter a whole parent object(ZoneDTO) with a list of child(ZonePoint_DTO) entities. Please note the I have set cascade type to Cascade.PERSIST.
I am using JpaRepository.save() to save Parent(ZoneDTO)
Below are the entity classes.
#Entity
#Getter
#Setter
public class ZoneDTO implements GenericPJM<ZoneDTO_Id>, Persistable<ZoneDTO_Id> {
private static Set<String> zoneNamesList;
#EmbeddedId private ZoneDTO_Id id;
private Integer noOfPolygons;
#OneToMany(mappedBy = "zoneDTO", fetch = FetchType.EAGER)
#Cascade(value = org.hibernate.annotations.CascadeType.PERSIST)
private List<ZonePointDTO> zonePointsList;
#Override
public boolean isNew() {
boolean result = !zoneNamesList.contains(id.getZoneName());
System.out.println(
"__________________________isNew() called for " + this.getClass().getSimpleName() + "result= " + result);
return result;
}
public static void setZoneNamesList(Set<String> zoneNamesListNew) {
zoneNamesList = zoneNamesListNew;
}
}
#Getter
#Setter
#Embeddable
public class ZoneDTO_Id implements GenericPJM_Id {
String zoneName;
}
#Entity
#Getter
#Setter
public class ZonePointDTO implements GenericPJM<Long> {
#JsonIgnore #Id #GeneratedValue private Long id;
private double xValue;
private double yValue;
private Integer polygonNumber;
#ManyToOne private ZoneDTO zoneDTO;
}
The JSON format I am sending:
{
"type":"ZoneDTO",
"id": {"zoneName":"SAMPLE"},
"noOfPolygons": 1,
"zonePointsList": [
{
"type":"ZonePointDTO",
"xValue": 10,
"yValue": 39.5,
"polygonNumber": 1
},
{
"type":"ZonePointDTO",
"xValue": 10,
"yValue": 39,
"polygonNumber": 1
}
]
}
I want to cascade persist the parent object(ZoneDTO), all at once and hopefully minimal DB calls.
Currently, data is getting persisted, both parent and child. But the child mapping column is saving nulls.
Any comments or workarounds or other methods will be helpful.
Thanks in advance.
My Organization entity
#Entity
public class Organization implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
private String name;
private String type;
private byte image;
#OneToMany(cascade=CascadeType.MERGE)
#JoinColumn(name="ORGANIZATION_ID")
private List<User> admin;
My User entity
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
private String score;
private boolean online;
private String resume;
public Status status;
public enum Status {
ACTIVE, INACTIVE, VERIFIED, NOT_VERIFIED , BANNED
};
#ManyToOne(cascade=CascadeType.MERGE)
#JoinColumn(name="ORGANIZATION_ID")
private Organization organization;
#Persistent
private User_personal user_p;
public User_personal getUser_personal(){
return user_p;
}
public void setUser_personal(User_personal user_p) {
this.user_p = user_p;
}
#OneToMany(mappedBy = "user", cascade=CascadeType.MERGE)
private List<Project> projects;
I got the one-Many relation between User and Projects correctly but not working for User and Organization(many-one).I am getting error like this
WARNING: /OrganizationServlet
javax.persistence.PersistenceException: Detected attempt to establish
Organization(no-id-yet) as the parent of User(4793870697103360) but the
entity identified by User(4793870697103360) has already been persisted
without a parent. A parent cannot be established or changed once an object
has been persisted.at...
showing error at em.getTransaction().commit();.
My servlet is
protected void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
// TODO Auto-generated method stub
HashMap<String,String> map = Request_to_map.getBody(request);
boolean validToken = JWT.parseJWT(request.getHeader("Access-token")
,map.get("email"));
JsonObject output = new JsonObject();
List<User> organization_admin = new ArrayList<User>();
if(validToken == true){
EntityManager em;
em = EMF.get().createEntityManager();
String organizationName = map.get("name");
String type = map.get("type");
byte image = 0;
if(map.get("image")!=null)
{
image = Byte.valueOf(map.get("image"));
}
String email = map.get("email");
if(organizationName==null||type==null||email==null||map.get("image")==null)
{
throw new IllegalArgumentException("please fill required
details");
}
try{
em.getTransaction().begin();
User user = User.find(email, em);
if(user!=null)
{
Organization.org_status status= org_status.ACTIVE;
Organization organization = new
Organization(organizationName, type,image,status);
user.setOrganization(organization);
organization_admin = organization.getAdmin();
if(organization_admin == null)
{
organization_admin = new ArrayList<User>();
}
organization_admin .add(user);
organization.setAdmin(organization_admin);
em.persist(organization);
em.persist(user);
output.addProperty("message", "done");
em.getTransaction().commit();
}
else
output.addProperty("message","No such User found.Please
check details provided");
}
finally{
if(em.getTransaction().isActive())
em.getTransaction().rollback();
// em.close();
}
}
else
output.addProperty(Constants.MESSAGE,
Constants.TokenNotAuthenticated);
response.setContentType("application/Json");
response.getWriter().println(output);
}
Can anyone help me in getting this? When user is created I am getting ORGANIZATION_ID as a column but cant create entity of organization.I dont think joins are to be used as GAE doesn't allow it.
Consider a simple domain class:-
#Document(collection = "#{T(demo.TenantGenerator).tenant()}Employee")
public class Employee implements Serializable{
/**
* serialVersionUID
*/
private static final long serialVersionUID = -6236812201549032402L;
#Id
private String id;
protected String name;
/**
* #return the name
*/
public String getName() {
return name;
}
/**
* #param name the name to set
*/
public void setName(String name) {
this.name = name;
}
}
Here I have configured dynamic collection name using SpEL targeting a static method:-
public class TenantGenerator {
public static String tenant = "";
public static final String tenant(){
return tenant;
}
}
This is how my repository interface looks like:-
public interface EmployeeRepository extends MongoRepository<Employee,String> {
#Query(value="{'name':?0}")
public List<Employee> someMethod(String id);
}
My test code:-
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
public class ApplicationTests {
#Autowired
private EmployeeRepository repo;
#Autowired
private MongoTemplate template;
#Test
public void contextLoads() {
// Set the collection with ABC
TenantGenerator.tenant = "ABC";
Employee e = new Employee();
e.setName("test");
repo.save(e);
Employee ee = new Employee();
e.setName("test");
repo.save(ee);
List<Employee> findAll = repo.findAll();
System.out.println(findAll.size());
Employee eee = new Employee();
e.setName("test");
template.save(eee,"customercoll");
System.out.println(repo.someMethod("test"));
//Set collection name with XYZ
TenantGenerator.tenant = "XYZ";
System.out.println(repo.someMethod("test")); // PROBLEM this should try to get from XYZ. But instead tries to fetch from ABC itself
System.out.println(repo.findAll());
}
}
PROBLEM
The collection name is correctly picked at run time except case when I a method in my repository that has #Query annotation.
While trying to debug the Spring Data Mongo code , I found that for #Query annotated methods,org.springframework.data.mongodb.repository.query.MongoQueryMethod caches the collection information inside it in property org.springframework.data.mongodb.repository.query.MongoQueryMethod.metadata.
So while trying to get org.springframework.data.mongodb.repository.query.MongoQueryMethod.getEntityInformation() during query execution, collection name is not fetched using the SpEL the second time.
Relavant piece of code:-
#Override
#SuppressWarnings("unchecked")
public MongoEntityMetadata<?> getEntityInformation() {
if (metadata == null) { // !!!THIS IS PROBLEM FOR ME!!!!
Class<?> returnedObjectType = getReturnedObjectType();
Class<?> domainClass = getDomainClass();
MongoPersistentEntity<?> returnedEntity = mappingContext.getPersistentEntity(getReturnedObjectType());
MongoPersistentEntity<?> managedEntity = mappingContext.getPersistentEntity(domainClass);
returnedEntity = returnedEntity == null ? managedEntity : returnedEntity;
MongoPersistentEntity<?> collectionEntity = domainClass.isAssignableFrom(returnedObjectType) ? returnedEntity
: managedEntity;
this.metadata = new SimpleMongoEntityMetadata<Object>((Class<Object>) returnedEntity.getType(),
collectionEntity.getCollection());
}
return this.metadata;
}
WHAT I WANT
How can I pick collection name at run time even for the #Query annotated repository methods?
Thanks
Looks like you're running into this issue which has been fixed recently, was just released in Spring Data MongoDB 1.7 M1 and is scheduled to be in to-be-released 1.6.2 and 1.5.5. Feel free to give the milestones a spin.