JPA Cascading removal of parent and Child - jpa-2.0

I'm trying to learn and understand JPA, and just have a couple of questions regarding deleting a parent and its children in one go. I'm using OpenJPA and EJB3. I have two entities, a Category and a Product. A Category contains many products and a product has a reference to its parent category. The category's list of products is set to cascade.
//Category
#Entity #NamedQueries({#NamedQuery(name = "Category.getCategoryByName", query = "SELECT c FROM Category c WHERE c.name = :name"),#NamedQuery(name = "Category.getCategoryByCategoryId", query = "SELECT c FROM Category c WHERE c.categoryid = :categoryid"), #NamedQuery(name = "Category.getAllCategories", query = "SELECT c FROM Category c left join fetch c.products")})
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=IDENTITY)
private Integer categoryid;
private String name;
//bi-directional many-to-one association to Product
#OneToMany(cascade={CascadeType.ALL}, orphanRemoval = true,
fetch = EAGER, mappedBy="category")
private List<Product> products;
}
//Product
#Entity
#NamedQueries({#NamedQuery(name = "Product.getProductsByCategory",
query = "SELECT p.code, p.description, p.name, p.productid, p.weight FROM Product p WHERE p.category.categoryid = :category_categoryid"),
#NamedQuery(name = "Product.getProductByName", query = "SELECT p FROM Product p WHERE p.name = :name"),
#NamedQuery(name = "Product.getProductByCode", query = "SELECT p FROM Product p WHERE p.code = :code"),
#NamedQuery(name = "Product.getProductByProductId", query = "SELECT p FROM Product p WHERE p.productid = :productid"),
#NamedQuery(name = "Product.getAllProducts", query = "SELECT p FROM Product p")})
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=IDENTITY)
private Integer productid;
private String code;
private String description;
private String name;
private Double weight;
//bi-directional many-to-one association to Category
#ManyToOne(optional = false)
#JoinColumn(name="CATEGORYID")
private Category category;
}
}
// The EJB
#Stateless
#LocalBean
public class ShopManagerBean implements Serializable {
#PersistenceContext(unitName = "TestEJBProject2", type = PersistenceContextType.TRANSACTION)
private EntityManager entityManager;
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void deleteCategory(Category category)
throws TestApplicationException {
try {
Category actualCat = entityManager.find(Category.class,
category.getCategoryid());
List<Product> products = actualCat.getProducts();
if (products != null) {
Iterator<Product> it = products.iterator();
while (it.hasNext()) {
Product p = it.next();
it.remove();
entityManager.remove(p);
}
}
entityManager.refresh(actualCat);
entityManager.remove(actualCat);
} catch (Exception e) {
e.printStackTrace();
throw new TestApplicationException("Error creating new Product", e);
}
}
}
If I use the following code in the deleteCategory method the EJB then I cannot delete the parent and children as I get an Optimistic Locking exception (An optimistic lock violation was detected when flushing object instance "entity.Product-101" to the data store. This indicates that the object was concurrently modified in another transaction.) - complaining about flushing the product child to the data store
Category actualCat = entityManager.find(Category.class, category.getCategoryid());
if (products != null) {
actualCat.getProducts().clear();
}
entityManager.remove(actualCat);
However, if I use the following code in the deleteCategory method then I can delete the parent and children...but only if I call entityManager.refresh(actualCat) after removing the children and before removing the parent (otherwise I get an optimistic locking exception). Could somebody please explain to me why this is the case and also what the correct/best way of doing a cascading delete with OpenJPA V2 would be?
Category actualCat = entityManager.find(Category.class, category.getCategoryid());
List<Product> products = actualCat.getProducts();
if (products != null) {
Iterator<Product> it = products.iterator();
while (it.hasNext()) {
Product p = it.next();
it.remove();
entityManager.remove(p);
}
}
entityManager.refresh(actualCat);
entityManager.remove(actualCat);
Thanks in advance for your help
Fais
Addition
Here is the db creation script:
--
CREATE SCHEMA "DB2ADMIN";
CREATE TABLE "DB2ADMIN"."CATEGORY" (
"CATEGORYID" INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY ( START WITH 1 INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 NO CYCLE CACHE 20),
"NAME" VARCHAR(50) NOT NULL
)
DATA CAPTURE NONE;
CREATE TABLE "DB2ADMIN"."PRODUCT" (
"PRODUCTID" INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY ( START WITH 1 INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 NO CYCLE CACHE 20),
"CODE" CHAR(15) NOT NULL,
"NAME" VARCHAR(50) NOT NULL,
"DESCRIPTION" VARCHAR(200) NOT NULL,
"WEIGHT" FLOAT(53) NOT NULL,
"CATEGORYID" INTEGER NOT NULL
)
DATA CAPTURE NONE;
ALTER TABLE "DB2ADMIN"."CATEGORY" ADD CONSTRAINT "CATEGORY_PK" PRIMARY KEY
("CATEGORYID");
ALTER TABLE "DB2ADMIN"."PRODUCT" ADD CONSTRAINT "PRODUCT_PK" PRIMARY KEY
("PRODUCTID");
ALTER TABLE "DB2ADMIN"."PRODUCT" ADD CONSTRAINT "PRODUCT_CATEGORY_FK" FOREIGN KEY
("CATEGORYID")
REFERENCES "DB2ADMIN"."CATEGORY"
("CATEGORYID")
ON DELETE CASCADE;

Related

Can we check double creation of a state in Corda?

In my use case, I have a student enrolling to a specific program which in turn creates a student state on the ledger.
Now, if the same student enrolls with same credentials again, I want to avoid it and throw some exception or message.
One solution, I can think of is, I can query the vault before creating the student state and if that student is not found in the ledger than only he is allowed to enroll.
But, this seems like a vague idea.
Can someone provide a better approach or some other way I am not aware of?
You should implement schema for your state as in this example:
class CashState(
val owner: AbstractParty,
val pennies: Long) : ContractState, QueryableState {
override val participants get() = listOf(owner)
override fun generateMappedObject(schema: MappedSchema): PersistentState {
return when (schema) {
is CashSchemaV1 -> CashSchemaV1.PersistentCashState(
this.owner,
this.pennies
)
else -> throw IllegalArgumentException("Unrecognised schema $schema")
}
}
override fun supportedSchemas(): Iterable<MappedSchema> = listOf(CashSchemaV1)
}
The schema itself:
object CashSchema
#CordaSerializable
object CashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCashState::class.java)) {
#Entity
#Table(name = "contract_cash_states")
class PersistentCashState(
#Column(name = "owner_name", unique=true, nullable = true)
var owner: AbstractParty?,
#Column(name = "pennies", nullable = false)
var pennies: Long
) : PersistentState()
}
The key point is to make the columns unique, so when you add duplicate value the exception is thrown.
docs

Do I need to Set Foreign key value in JPA?

I have two table:
CREATE TABLE [LeTYPE](
[LeNAME] [varchar](100) NOT NULL,
[Le_DESC] [varchar](500) NULL,
[LeFOR] [varchar](50) NOT NULL,
CONSTRAINT [PK_LeTYPE] PRIMARY KEY CLUSTERED
(
[LeNAME] ASC
)
)
CREATE TABLE [Le](
[SN] [int] IDENTITY(1,1) NOT NULL,
[LeNAME_FK] [varchar](100) NOT NULL,
[Le_SN] [int] NULL,
[LOWERRANGE] [float] NOT NULL,
[UPPERRANGE] [float] NOT NULL,
[Le_DESC] [varchar](500) NULL,
[COLOR] [varchar](45) NULL,
CONSTRAINT [Le_pk] PRIMARY KEY CLUSTERED
(
[SN] ASC
))
GO
ALTER TABLE [Le] WITH CHECK ADD CONSTRAINT [FK_Le_LeTYPE] FOREIGN KEY([LeNAME_FK])
REFERENCES [LeTYPE] ([LeNAME])
ON UPDATE CASCADE
ON DELETE CASCADE
GO
ALTER TABLE [Le] CHECK CONSTRAINT [FK_Le_LeTYPE]
GO
One tuple in LETYPE will have many LE.
JPA Entity generated by netbeans:
public class Letype implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 100)
#Column(nullable = false, length = 100)
private String Lename;
#Size(max = 500)
#Column(name = "Le_DESC", length = 500)
private String LeDesc;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 50)
#Column(nullable = false, length = 50)
private String Lefor;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "LenameFk", fetch = FetchType.LAZY)
private List<Le> LeList;
}
public class Le implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#NotNull
#Column(nullable = false)
private Integer sn;
#Column(name = "Le_SN")
private Integer LeSn;
#Basic(optional = false)
#NotNull
#Column(nullable = false)
private double lowerrange;
#Basic(optional = false)
#NotNull
#Column(nullable = false)
private double upperrange;
#Size(max = 500)
#Column(name = "Le_DESC", length = 500)
private String LeDesc;
#Size(max = 45)
#Column(length = 45)
private String color;
#JoinColumn(name = "LeNAME_FK", referencedColumnName = "LeNAME", nullable = false)
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private Letype LenameFk;
}
Now, What I wanted was if I add a LETYPE from JSF view I would like to add multiple LE also at the same time.
LETYPE
-LE1
-LE2
-LE3
Do I need to set LenameFk manually in Le entity since I am getting
*Cannot insert the value NULL into column 'LENAME_FK'*? Why won't it automatically take it from Le enityt?
Note this snippet of code:
public class Le implements Serializable {
...
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private Letype LenameFk;
...
}
optional = false means any instance of this entity must participate the relation, so, the foreign key field can not be null.
Your right, You need to set LenameFk manually in Le entity.
In General , for Bi-directional one-to-many two way relation , Accessor method should like below and assume entities are Customer and Order and one-to-many relation b/w them.
Customer.java
public Collection<Order> getOrders() {
return Collections.unmodifiableCollection(orders);
}
public void addToOrders(Order value) {
if (!orders.contains(value)) {
orders.add(value);
value.setCustomer(this);
}
}
public void removeFromOrders(Order value) {
if (orders.remove(value)) {
value.setCustomer(null);
}
}
Order.java
public void setCustomer(Customer value) {
if (this.customer != value) {
if (this.customer != null) {
this.customer.removeFromOrders(this);
}
this.customer = value;
if (value != null) {
value.addToOrders(this);
}
}
}
public Customer getCustomer() {
return customer;
}

JPA, compound key with foreign keys + persist oneToMany

I'm trying to do something with JPA that'll use a lot on a project but I'm stuck.
I have 2 entities + a kind of "glue" entity, I'll call them
ClassA
ClassB
Glue
I want to add a new ClassA with new Glues set in it's list, ClassB's already exist.
That would do something like :
ClassA 1 | Glue 1 1 | ClassB 1
ClassA 1 | Glue 1 2 | ClassB 2
ClassA 1 | Glue 1 3 | ClassB 3
ClassA 1 | Glue 1 4 | ClassB 4
So as said ClassA and all Glues are to be inserted, ClassA has a List with the new Glues to be inserted.
Here they are :
#Entity
public class ClassA implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
(...)
#OneToMany(cascade = CascadeType.ALL, mappedBy = "classA")
private List<Glue> glueList;
(...)
}
#Entity
public class ClassB implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
(...)
}
#Entity
public class Glue implements Serializable {
#EmbeddedId
protected GluePK gluePK;
#JoinColumn(name = "id_class_a", referencedColumnName = "id", nullable = false, insertable = false, updatable = false)
#ManyToOne(optional = false)
private ClassA classA;
#JoinColumn(name = "id_class_b", referencedColumnName = "id", nullable = false, insertable = false, updatable = false)
#ManyToOne(optional = false)
private ClassB classB;
(...)
}
#Embeddable
public class GluePK implements Serializable {
#Basic(optional = false)
#NotNull
#Column(name = "id_class_a", nullable = false)
private int idClassA;
#Basic(optional = false)
#NotNull
#Column(name = "id_class_b", nullable = false)
private int idClassB;
(...)
}
When I try to persist my ClassA I'm getting something like :
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:
Cannot add or update a child row: a foreign key constraint fails (bdd.glue, CONSTRAINT constraint_name FOREIGN KEY (id_class_a) REFERENCES ClassA (id) ON DELETE NO ACTION ON UPDATE NO ACTION)
I understand that he complains that Glues dont have ClassA's reference set but I'd like him to fill it just then he persists ClassA.
Is this achievable?
If not what's the best way to do it?
I'd like to stay on JPA without any specific vendor tricks (I'm using eclipselink) but if some vendor can do it easily I'll go for it.
Thanks!
I would remove the EmbeddedId, use an IdClass instead and just add the #Id to the #ManyToOne mappings.
See,
http://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#JPA_2.0
Or maybe even give Glue an id of its own.
You could also remove the insertable = false, updatable = false from the #ManyToOne and move them to the EmbeddedId.

Not Null Property Exception with JPA onetoone mapping

I am trying my way around JPA but I cant get this to work the way I understand them.
Its the onetoone bidirectional mapping between an Order and an OrderInvoice class which is a required
association
My entities are marked as this
#Entity
#Table(name = "Orders")
public class Order {
#Id
#GeneratedValue
#Column(name = "ORDER_ID")
private int orderId;
#OneToOne(optional=false,cascade=CascadeType.ALL, mappedBy="order", targetEntity=OrderInvoice.class)
private OrderInvoice invoice;
}
#Entity
#Table(name = "ORDER_INVOICE")
public class OrderInvoice {
#Id
#GeneratedValue
#Column(name = "INVOICE_ID", nullable = false)
private int invoiceId;
#OneToOne(optional = false)
#JoinColumn(name="ORDER_ID")
private Order order;
}
My test class is like this.
#Test
public void createOrder() {
Order order = createOrderImpl();
assertNotNull(order);
}
private Order createOrderImpl() {
OrderInvoice orderInvoice = new OrderInvoice(new Date(), 100.0, null,
null, new Date());
Order order = new Order(100.0, "JOHN Doe's Order", new Date(), new Date(),orderInvoice);
orderDao.create(order);
return order;
}
But I am encountering below problem when I run my Test
javax.persistence.PersistenceException: org.hibernate.PropertyValueException: not-null property references a null or transient value: order.OrderInvoice.order
at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:614)
Caused by: org.hibernate.PropertyValueException: not-null property references a null or transient value: order.OrderInvoice.order
at org.hibernate.engine.Nullability.checkNullability(Nullability.java:95)
try to
orderInvoice.setOrder(order);
orderDao.create(order);

JPA: persist does not insert into join table

All,
I am using JPA for this application and annotations for Mapping entities. I have an entity called UserStory and another one called Revision. There is a OneToMany for UserStory to Revision.
#Entity
#Table(name = "user_story")
#NamedNativeQueries({
#NamedNativeQuery(name = "storyBacklog", query = "SELECT userstory.rank AS rank, userstory.description AS description, userstory.estimate AS estimate, userstory.name AS name, "
+ "userstory.id AS id, userstory.status AS status FROM user_story userstory ORDER BY userstory.rank ASC", resultClass = UserStory.class),
#NamedNativeQuery(name = "getCos", query = "SELECT conditions.cos As cos FROM story_cos conditions WHERE conditions.story_id=?1", resultSetMapping = "cosMapping") })
#SqlResultSetMappings({ #SqlResultSetMapping(name = "cosMapping", columns = #ColumnResult(name = "cos")) })
public class UserStory implements Serializable {
private static final long serialVersionUID = 248298400283358441L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
...
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "story_revisions", joinColumns = #JoinColumn(name = "story_id"), inverseJoinColumns = #JoinColumn(name = "revision_id"))
private Set<Revision> revisions;
here's Revision entity:
#Entity
#Table(name = "revision")
public class Revision implements Serializable {
private static final long serialVersionUID = -1823230375873326645L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false)
private String description;
#Column(name = "date_created", nullable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
When I create a userStory; I add a revision on to it,
but the join table is not populated unless, I persist story first,
then add revision and merge it.
here's the code for saving a UserStory:
public UserStory saveUserStory(UserStory userStory) {
Revision revision = new Revision();
revision.setCreationDate(new Timestamp(System.currentTimeMillis()));
revision.setDescription("User story created");
Set<Revision> revisions = new HashSet<Revision>();
revisions.add(revision);
userStory.setRevisions(revisions);
return storyDao.create(userStory);
}
in StoryDao I call the persist method:
#Transactional(readOnly = false)
public UserStory create(UserStory userStory) {
if (userStory.getRank() == null) {
Integer highestRank = 0;
highestRank = (Integer) entityManager.createNativeQuery("select max(rank) from user_story")
.getSingleResult();
if (highestRank != null)
highestRank += 1;
else
highestRank = new Integer(1);
userStory.setRank(highestRank);
}
entityManager.persist(userStory);
LOGGER.debug("Added User Story with id " + userStory.getId());
entityManager.detach(userStory);
return userStory;
}
here's the SQL from LOGS
Hibernate:
insert
into
user_story
(description, estimate, name, rank, status)
values
(?, ?, ?, ?, ?)
Hibernate:
insert
into
revision
(date_created, description)
values
(?, ?)
Hibernate:
select
revision0_.id as id5_0_,
revision0_.date_created as date2_5_0_,
revision0_.description as descript3_5_0_
from
revision revision0_
where
revision0_.id=?
Hibernate:
select
userstory0_.id as id3_1_,
userstory0_.description as descript2_3_1_,
userstory0_.estimate as estimate3_1_,
userstory0_.name as name3_1_,
userstory0_.rank as rank3_1_,
userstory0_.status as status3_1_,
revisions1_.story_id as story1_3_3_,
revision2_.id as revision2_3_,
revision2_.id as id5_0_,
revision2_.date_created as date2_5_0_,
revision2_.description as descript3_5_0_
from
user_story userstory0_
left outer join
story_revisions revisions1_
on userstory0_.id=revisions1_.story_id
left outer join
revision revision2_
on revisions1_.revision_id=revision2_.id
where
userstory0_.id=?
I can see from here it saves the user story and revision, but then tries to run a join to see if the relation exists before doing an insert into the join table. Which of course it will not find because I am creating this object.
How do it get the join table populated in this case?
Works now. Here's the updated code
revisions.add(revision);
userStory = storyDao.create(userStory);
userStory.setRevisions(revisions);
return storyDao.update(userStory);
I am still not sure why this is required; the two step method where I persist an object then update it.