Unexpected behaviour of EntityManager.merge() - jpa-2.0

I am using embedded glassfish (3.1.2.2) with junit (4.11), with JDK 1.7, though my source and target is set to 1.6 (maven-compiler-plugin configuration).
Following is my code:
Person.java
#Entity
public class Person implements Serializable {
private static final long serialVersionUID = 81398385247591972L;
#Id
#GeneratedValue
private Long id;
#Version
private Long version;
#Column(length = 15, nullable = false, unique = true, updatable = false)
private String username;
#Column(length = 50)
private String status;
// Constructors
// getters/setters
// hashCode, equals, toString
}
Service.java
#Stateless
public class Service {
#PersistenceContext(unitName = "ExamplePU", type = PersistenceContextType.TRANSACTION)
private EntityManager em;
public Person add(Person person) {
em.persist(person);
return person;
}
public Person find(Long id) {
return em.find(Person.class, id);
}
public Person modify(Person person) {
return em.merge(person);
}
// some more code ...
}
ServiceTest.java
public class ServiceTest {
private static EJBContainer ejbContainer;
private static Service service;
// #BeforeClass, #AfterClass, #Before, #After
#Test
public void testMerge() {
Person person;
/* Step 1 */person = service.add(new Person("username", "status"));
print("Added : " + person);
person.setStatus("Away");
/* Step 2 */person = service.modify(person);
print("Merged (status change) : " + person);
person.setUsername("UsErNaMe");
/* Step 3 */person = service.modify(person);
print("Merged (username change) : " + person);
}
// Some more tests
}
Step 1 generates following SQL (as expected):
INSERT INTO PERSON (ID, STATUS, USERNAME, VERSION) VALUES (?, ?, ?, ?)
bind => [1, status, username, 1]
Step 2 generates following SQL (as expected):
UPDATE PERSON SET STATUS = ?, VERSION = ? WHERE ((ID = ?) AND (VERSION = ?))
bind => [Away, 2, 1, 1]
Step 3 does not generate any SQL, but it does not throw any exception, which I am expecting, as the 'username' is annotated as #Column(..., updatable = false). The print(...) method prints following output:
Merged (username change) : Person [id=1, version=2, username=UsErNaMe, status=Away]
This time the merge() operation has updated username, but not version. Also, now the database is out-of-sync with EntityManager cache.
Is this expected, or bug in EclipseLink?
UPDATE
Expected result is exception at Step 3 above.
UPDATE
Have filed bug here.

You marked the column as non-updatable, and EclipseLink detects that the only change made to the person you tell it to merge is the user name. But the user name must not be updated. So it doesn't issue any SQL update query.
If you mark a column as non-updatable, you shouldn't update it.
So, to make things clear, the behavior you observe is the expected behavior.

Related

How to test an Update class in apex

Hello I am new to apex and soql, I would like some help on my test class, below is the code i am having trouble with,
public class UpdateMyCard {
#AuraEnabled(cacheable=false)
public static Card__c updateCard(Double Amount){
String userId = UserInfo.getUserId();
Card__c myCard = [SELECT Id, Card_no__c, total_spend__c From Card__c Where OwnerId =:userId];
myCard.total_spend__c = myCard.total_spend__c + Amount;
try{
update myCard;
}catch (Exception e) {
System.debug('unable to update the record due to'+e.getMessage());
}
return myCard;
}
}
Test Class
#isTest
public static void updatecard(){
UpdateMyCard.updateCard(200);
}
Error:
System.QueryException: List has no rows for assignment to SObject
Your code assumes there's already a Card record for this user. And it's exactly 1 record (if you have 2 your query will fail). I'm not sure what's your business requirement. Is it guaranteed there will always be such record? Should the code try to create one on the fly if none found? You might have to make that "updateCard" bit more error-resistant. Is the total spend field marked as required? Because "null + 5 = exception"
But anyway - to fix your problem you need something like that in the test.
#isTest
public static void updatecard(){
Card__c = new Card__c(total_spend__c = 100);
insert c;
UpdateMyCard.updateCard(200);
c = [SELECT total_spend__c FROM Card__c WHERE Id = :c.Id];
System.assertEquals(300, c.total_spend__c);
}

How do get rid of java.lang.IllegalArgumentException: Unable to match key(s) in corda?

I have created a counterparty session, issuer signs the transaction by passing in its key to the signInitialTransaction. Then when I call the CollectSignaturesFlow to get the buyer's signature, it throws 'Unable to match key(s)' exception.
No idea what went wrong.
This is my initiator flow.
package com.template.flows;
#InitiatingFlow
#StartableByRPC
public class InitiateTicketMovementFlow extends FlowLogic<String> {
private final String buyer;
private final String issuer;
private final StateRef assetReference;
public InitiateTicketMovementFlow(String buyer, String issuer, String hash, int index) {
this.buyer = buyer;
this.issuer = issuer;
this.assetReference = new StateRef(SecureHash.parse(hash), index);
}
#Override
#Suspendable
public String call() throws FlowException {
final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
AccountInfo issuerAccountInfo = UtilitiesKt.getAccountService(this)
.accountInfo(issuer).get(0).getState().getData();
AccountInfo receiverAccountInfo = UtilitiesKt.getAccountService(this)
.accountInfo(buyer).get(0).getState().getData();
AnonymousParty buyerAccount = subFlow(new RequestKeyForAccount(receiverAccountInfo));
QueryCriteria.VaultQueryCriteria queryCriteria = new QueryCriteria.VaultQueryCriteria()
.withStateRefs(ImmutableList.of(assetReference));
StateAndRef<CustomTicket> ticketStateStateAndRef = getServiceHub().getVaultService()
.queryBy(CustomTicket.class, queryCriteria).getStates().get(0);
CustomTicket ticketState = ticketStateStateAndRef.getState().getData();
TransactionBuilder txBuilder = new TransactionBuilder(notary);
MoveTokensUtilities.addMoveNonFungibleTokens(txBuilder, getServiceHub(),
ticketState.toPointer(CustomTicket.class), receiverAccountInfo.getHost());
FlowSession buyerSession = initiateFlow(receiverAccountInfo.getHost());
buyerSession.send(ticketState.getValuation());
List<StateAndRef<FungibleToken>> inputs = subFlow(new ReceiveStateAndRefFlow<>(buyerSession));
List<FungibleToken> moneyReceived = buyerSession.receive(List.class).unwrap(value -> value);
MoveTokensUtilities.addMoveTokens(txBuilder, inputs, moneyReceived);
SignedTransaction selfSignedTransaction = getServiceHub().
signInitialTransaction(txBuilder, ImmutableList.of(issuerAccountInfo.getHost().getOwningKey()));
SignedTransaction signedTransaction = subFlow(new CollectSignaturesFlow(
selfSignedTransaction, Arrays.asList(buyerSession), Collections.singleton(issuerAccountInfo.getHost().getOwningKey())));
SignedTransaction stx = subFlow(new FinalityFlow(
signedTransaction, ImmutableList.of(buyerSession)));
subFlow(new UpdateDistributionListFlow(stx));
return "\nTicket is sold to "+ buyer;
}
}
It SEEMS like the issue here is that you're getting the buyer account the wrong way? Or that the finality flow call might be off. Take a look at our samples on this.
Maybe try something like this to get your account info
AccountInfo targetAccount = accountService.accountInfo(<STRING NAME OF ACCOUNT >).get(0);
src is our corda samples repo: https://github.com/corda/samples-java/blob/master/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendShippingRequest.java#L80
Also, note how different the finality calls look like:
https://github.com/corda/samples-java/blob/master/Accounts/supplychain/workflows/src/main/java/net/corda/samples/supplychain/flows/SendShippingRequest.java#L112

JPQL left Join and Criteria API joins (Left and inner) throwing extra db queries to initialise associations

I am having below two entities .Instructor entity has relationship oneToMany with Vehicle entity.
public class Instructor {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
#GenericGenerator(name = "native", strategy = "native")
private int id;
#Version
#Column(columnDefinition = "int(11) not null default 0")
private int version = 0;
#OneToMany(mappedBy = "instructor", orphanRemoval = true, cascade = CascadeType.ALL)
private Set<Vehicle> vehicles = new HashSet<>();
.....
public class Vehicle {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator =
"native")
#GenericGenerator(name = "native", strategy = "native")
private int id;
#ManyToOne(fetch = FetchType.LAZY)
private Student student;
#ManyToOne(fetch = FetchType.LAZY)
private Instructor instructor;
I want to fetch all instructors along with their vehicles in one query .
I have tried below four ways for doing this but i am not able to do this with test case 2 , 3 and 4 .
Test Case 1 : With JPQL join fetch clause.
#Test
#Transactional
#Rollback(false)
public void fetchTest1(){
List<Instructor> instructorsJpqlJoinFetch = em
.createQuery("select distinct i from
Instructor i join fetch i.vehicles v ",
Instructor.class)
.getResultList();
print(instructorsJpqlJoinFetch);
}
private void print(List<Instructor> instructors) {
instructors.forEach(i -> {
System.out.println("######Instructor Name : " + i.getName());
i.getVehicles().forEach(v -> {
System.out.println("######Instructor Vehicle
Number : " + v.getVehicleNumber());
});
});
Db Query gone to DB for case 1 :
select
distinct instructor0_.id as id1_2_0_,
vehicles1_.id as id1_5_1_,
instructor0_.address as address2_2_0_,
instructor0_.birth_date_time as birth_da3_2_0_,
instructor0_.birth_date_time_zone_offset as birth_da4_2_0_,
instructor0_.created_date as created_5_2_0_,
instructor0_.day_off_time as day_off_6_2_0_,
instructor0_.day_start_time as day_star7_2_0_,
instructor0_.father_name as father_n8_2_0_,
instructor0_.mother_name as mother_n9_2_0_,
instructor0_.name as name10_2_0_,
instructor0_.photo as photo11_2_0_,
instructor0_.monthly_salary as monthly12_2_0_,
instructor0_.updated_date as updated13_2_0_,
instructor0_.version as version14_2_0_,
vehicles1_.creation_date as creation2_5_1_,
vehicles1_.instructor_id as instruct8_5_1_,
vehicles1_.purchased_date_time as purchase3_5_1_,
vehicles1_.purchased_date_zone_offset as purchase4_5_1_,
vehicles1_.student_id as student_9_5_1_,
vehicles1_.updated_date as updated_5_5_1_,
vehicles1_.vechicle_type as vechicle6_5_1_,
vehicles1_.vehicle_number as vehicle_7_5_1_,
vehicles1_.instructor_id as instruct8_5_0__,
vehicles1_.id as id1_5_0__
from
instructor instructor0_
inner join
vehicle vehicles1_
on instructor0_.id=vehicles1_.instructor_id
As a result All instructors are fethched from db along with their vehicles
and after executing i.getVehicles() no query goes to db again . Which should be a correct behaviour . I am getting this behaviour with JPQL join fetch clause .
Test Case 2 : Tried same with Criteria API like below :
#Test
#Transactional
#Rollback(false)
public void fetchTest3() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Instructor> cq =
cb.createQuery(Instructor.class);
Root<Instructor> root = cq.from(Instructor.class);
root.join(Instructor_.vehicles);
List<Instructor> instructorsWithCriteria =
em.createQuery(cq.distinct(true).select(root)).
getResultList();
print(instructorsWithCriteria);
}
Db query gone to db for case 2 :
select distinct instructor0_.id as id1_2_,
instructor0_.address as address2_2_,
instructor0_.birth_date_time as birth_da3_2_,
instructor0_.birth_date_time_zone_offset as birth_da4_2_,
instructor0_.created_date as created_5_2_,
instructor0_.day_off_time as day_off_6_2_,
instructor0_.day_start_time as day_star7_2_,
instructor0_.father_name as father_n8_2_,
instructor0_.mother_name as mother_n9_2_,
instructor0_.name as name10_2_,
instructor0_.photo as photo11_2_,
instructor0_.monthly_salary as monthly12_2_,
instructor0_.updated_date as updated13_2_,
instructor0_.version as version14_2_
from
instructor instructor0_
inner join
vehicle vehicles1_
on instructor0_.id=vehicles1_.instructor_id
**As a result all instructors are fethched from db . As soon as i hit
i.getVehciles() in print method a query to get vehicles of this instructor goes to db . Same happens for all instructors one by one .
Same behaviour happens for case 3 and 4 below too .
What should be passed in select clause of case 2, 3, 4 so that vehicle columns also got selected in query ?
**
Test case 3: JPQL left join
#Test
#Transactional
#Rollback(false)
public void fetchTest2() {
List<Instructor> instructorsJpqlLeftJoin = em
.createQuery("select distinct i from Instructor i left join
i.vehicles v ", Instructor.class)
.getResultList();
print(instructorsJpqlLeftJoin);
}
Db Query to db for Case 3 :
select
distinct instructor0_.id as id1_2_,
instructor0_.address as address2_2_,
instructor0_.birth_date_time as birth_da3_2_,
instructor0_.birth_date_time_zone_offset as birth_da4_2_,
instructor0_.created_date as created_5_2_,
instructor0_.day_off_time as day_off_6_2_,
instructor0_.day_start_time as day_star7_2_,
instructor0_.father_name as father_n8_2_,
instructor0_.mother_name as mother_n9_2_,
instructor0_.name as name10_2_,
instructor0_.photo as photo11_2_,
instructor0_.monthly_salary as monthly12_2_,
instructor0_.updated_date as updated13_2_,
instructor0_.version as version14_2_
from
instructor instructor0_
left outer join
vehicle vehicles1_
on instructor0_.id=vehicles1_.instructor_id
Case 4 : Criteria API Left Join :
#Test
#Transactional
#Rollback(false)
public void fetchTest4() {
CriteriaBuilder cbLeftJoin = em.getCriteriaBuilder();
CriteriaQuery<Instructor> cqLeftJoin =
cbLeftJoin.createQuery(Instructor.class);
Root<Instructor> rootLeftJoin = cqLeftJoin.from(Instructor.class);
rootLeftJoin.join(Instructor_.vehicles, JoinType.LEFT);
List<Instructor> instructorsWithCriteriaLeftJoin = em
.createQuery(cqLeftJoin.distinct(true).
select(rootLeftJoin)).getResultList();
print(instructorsWithCriteriaLeftJoin);
}
Db Query for case 4 :
select
distinct instructor0_.id as id1_2_,
instructor0_.address as address2_2_,
instructor0_.birth_date_time as birth_da3_2_,
instructor0_.birth_date_time_zone_offset as birth_da4_2_,
instructor0_.created_date as created_5_2_,
instructor0_.day_off_time as day_off_6_2_,
instructor0_.day_start_time as day_star7_2_,
instructor0_.father_name as father_n8_2_,
instructor0_.mother_name as mother_n9_2_,
instructor0_.name as name10_2_,
instructor0_.photo as photo11_2_,
instructor0_.monthly_salary as monthly12_2_,
instructor0_.updated_date as updated13_2_,
instructor0_.version as version14_2_
from
instructor instructor0_
left outer join
vehicle vehicles1_
on instructor0_.id=vehicles1_.instructor_id
What should i do in case 2,3,4 so that vehicles columns also got selected in the same query so that no secondory selects go to db?

JPA - Unidirectional relationship containing List with duplicate entries

I have two entities, TestCase and TestCaseStep, defined as follows:
TestCase
#Entity
#Table(name = "TEST_CASE")
public class TestCase implements Serializable, TestCase
{
#Id
#Column(name = "name")
private String name;
#OneToMany(fetch = FetchType.EAGER)
#JoinTable(name = "Testcase_Step_Association", joinColumns = { #JoinColumn(name = "TC_NAME", referencedColumnName = "NAME") }, inverseJoinColumns = {
#JoinColumn(name = "STEP_NAME", referencedColumnName = "NAME") })
#OrderColumn(name = "STEP_NUMBER", nullable = false)
private List<TestCaseStep> testCaseSteps;
[...]
}
TestCaseStep
#Entity
#Table(name = "TEST_CASE_STEPS")
public class TestCaseStep implements Serializable, TestCaseStep
{
#Id
#Column(name = "name")
private String name;
[...]
}
This works fine as long as I do not try to insert the same object into the list of test case steps more than once. As soon as I try that, I get a primary key violation:
Caused by: org.h2.jdbc.JdbcSQLException: Eindeutiger Index oder Prim�rschl�ssel verletzt: "PRIMARY_KEY_9 ON PUBLIC.TESTCASE_STEP_ASSOCIATION(TC_NAME, TESTCASESTEPS_NAME) VALUES ('TESTCASE_1', 'OUT_STEP', 395)"
Unique index or primary key violation: "PRIMARY_KEY_9 ON PUBLIC.TESTCASE_STEP_ASSOCIATION(TC_NAME, TESTCASESTEPS_NAME) VALUES ('TESTCASE_1', 'OUT_STEP', 395)"; SQL statement:
INSERT INTO Testcase_Step_Association (testCaseSteps_NAME, TC_NAME, STEP_NUMBER) VALUES (?, ?, ?) [23505-191]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.index.BaseIndex.getDuplicateKeyException(BaseIndex.java:107)
at org.h2.mvstore.db.MVSecondaryIndex.checkUnique(MVSecondaryIndex.java:230)
at org.h2.mvstore.db.MVSecondaryIndex.add(MVSecondaryIndex.java:189)
at org.h2.mvstore.db.MVTable.addRow(MVTable.java:704)
at org.h2.command.dml.Insert.insertRows(Insert.java:156)
at org.h2.command.dml.Insert.update(Insert.java:114)
at org.h2.command.CommandContainer.update(CommandContainer.java:98)
at org.h2.command.Command.executeUpdate(Command.java:258)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:160)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:146)
at com.sun.gjc.spi.base.PreparedStatementWrapper.executeUpdate(PreparedStatementWrapper.java:125)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:892)
... 193 more
I need to allow my users to repeat the same test case step multiple times. How can I accomplish this without using a separate entity to map the relationship by myself?
Instead of defining name as a primary key in both TestCase and TestCaseSteps Entity,try to define a Surrogate Key as a primary key wherein you keep the Generation Strategy of that Surrogate Key to AUTO
In this way ,you always make a new Insert when you are rerunning test cases.

How can I override the test method name that appears on the TestNG report?

How can I override the test name that appears on the TestNG report? I want to override the name that appears in the middle column (currently shows as the method name). Is this even possible?
I tried to do it like this, but it didn't work.
public class EchApiTest1 extends TestBase {
...
#BeforeTest
public void setUp() {
restClient = new RestClientPost();
this.setTestName( "ech: XXXXXX" );
}
And, the base class:
import org.testng.ITest;
public class TestBase implements ITest {
String testName = "";
#Override
public String getTestName() {
return this.testName;
}
public void setTestName( String name ) {
this.testName = name;
}
}
NOTE: The above code does work when I am viewing the report detail in the Jenkins TestNG plugin report, which shows the overridden test name as a string called "Instance Name:" at the beginning of the Reporter log output. Why, in this case, WHY does a "setTestName()" method alter a string labeled "Instance Name" in the report?
One answer I found had a suggestion like this but I don't know how to pass an ITestResult arg to a AfterMethod method:
#AfterMethod
public void setResultTestName( ITestResult result ) {
try {
BaseTestMethod bm = (BaseTestMethod)result.getMethod();
Field f = bm.getClass().getSuperclass().getDeclaredField("m_methodName");
f.setAccessible(true);
f.set( bm, bm.getMethodName() + "." + your_customized_name );
} catch ( Exception ex ) {
Reporter.log( "ex" + ex.getMessage() );
}
Thoughts?
Please find following code for set custom name of testcase in TestNG reports.
Following features are available in this code.
Dynamic execution on same test-case in multiple time
Set custom test-case name for reports
Set parallel execution of multiple test-cases execution
import java.lang.reflect.Field;
import org.testng.ITest;
import org.testng.ITestResult;
import org.testng.Reporter;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;
import org.testng.internal.BaseTestMethod;
import com.test.data.ServiceProcessData;
public class ServiceTest implements ITest {
protected ServiceProcessData serviceProcessData;
protected String testCaseName = "";
#Test
public void executeServiceTest() {
System.out.println(this.serviceProcessData.toString());
}
#Factory(dataProvider = "processDataList")
public RiskServiceTest(ServiceProcessData serviceProcessData) {
this.serviceProcessData = serviceProcessData;
}
#DataProvider(name = "processDataList", parallel = true)
public static Object[] getProcessDataList() {
Object[] serviceProcessDataList = new Object[0];
//Set data in serviceProcessDataList
return serviceProcessDataList;
}
#Override
public String getTestName() {
this.testCaseName = "User custom testcase name";
// this.testCaseName = this.serviceProcessData.getTestCaseCustomName();
return this.testCaseName;
}
#AfterMethod(alwaysRun = true)
public void setResultTestName(ITestResult result) {
try {
BaseTestMethod baseTestMethod = (BaseTestMethod) result.getMethod();
Field f = baseTestMethod.getClass().getSuperclass().getDeclaredField("m_methodName");
f.setAccessible(true);
f.set(baseTestMethod, this.testCaseName);
} catch (Exception e) {
ErrorMessageHelper.getInstance().setErrorMessage(e);
Reporter.log("Exception : " + e.getMessage());
}
}}
Thanks
I found a "workaround" but I am hoping for a better answer. I want to be able to show this "test name" OR "instance name" value on the HTML report (not just within the Reporter.log output) and I am starting to think its not possible :
#Test(dataProvider = "restdata2")
public void testGetNameFromResponse( TestArguments testArgs ) {
this.setTestName( "ech: " + testArgs.getTestName() );
Reporter.log( getTestName() ); // this magic shows test name on report
....
With this workaround, the user can now identify which test it was by looking at the Reporter.log output but I still wish the name was more prominant.
I suspect the answer lies in writing a TestListenerAdapter that somehow overrides the ITestResult.getTestNameMethod() method? That is the holy grail I am looking for.
The ‘result’ object will automatically pass in the method setResultTestName( ITestResult result )
Make sure you put alwaysRun=true like the following when you have groups defined in your test class otherwise “AfterMethod” will not be excuted.
#AfterMethod (alwaysRun=true)