-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 10 posts ] 
Author Message
 Post subject: full outer join fails when first table listed is null
PostPosted: Tue Mar 08, 2016 11:35 am 
Newbie

Joined: Tue Mar 08, 2016 11:05 am
Posts: 5
Using Hibernate 5.0.7 Final with Java 1.8 and Wildfly 10.0.0Final and Eclipse Mars-1.
Transaction consists of 4 elements, a deposit which is made up of payments, which are made up of applications, which may or may not have adjustments. To retrieve all information for a report, following SQL is used:
Code:
SELECT adjustments.*, applications.*, payments.*, deposit.*, bank_account.bank.name
FROM schema.adjustments
FULL OUTER JOIN schema.applications USING (applications_serial)
FULL OUTER JOIN schema.payments USING (payments_serial)
FULL OUTER JOIN schema.deposit USING (deposit_serial)
JOIN schema.bank_account USING (bank_serial)
WHERE deposit.deposit_serial = ?;
Execution of SQL within the PostgreSQL database returns the correct results, in this case 12 rows. Within the rows, the adjustment data is all null, all other columns are valued. When executed through Hibernate, this results in 12 rows, but the 4 entities are all null, only the bank_name is valued.

A quick work-around is to change the order of the listed tables in the select portion of the statement, as in:
Code:
SELECT applications.*, payments.*, deposit.*,adjustments.* ...
This will return the correct results, but only because I can count of there being rows in the table for each of the tables except the adjustments table. It is also a bit misleading as the initial table in the lookup is not listed first. Since the SQL returns the query correctly, what am I missing in Hibernate that requires the entity sequence to be changed?


Top
 Profile  
 
 Post subject: Re: full outer join fails when first table listed is null
PostPosted: Tue Mar 08, 2016 12:07 pm 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1628
Location: Romania
As written in the User Guide, a @SqlResultSetMapping can give you much more control over how the SQL to entity properties mapping is done.


Top
 Profile  
 
 Post subject: Re: full outer join fails when first table listed is null
PostPosted: Tue Mar 08, 2016 2:39 pm 
Newbie

Joined: Tue Mar 08, 2016 11:05 am
Posts: 5
You are right, my bad for short-cutting the code, I didn't really know that there was another way besides SQLResultSetMapping, which we are using. Actual relevant code is produced below:
JPA mapping:
Code:
@NamedQuery(name="CashReceiptsDepo.findAll", query="SELECT c FROM CashReceiptsDepo c")
@SqlResultSetMappings({
   @SqlResultSetMapping(name = "CashReceiptsJournal", entities = {
         @EntityResult(entityClass = com.jmiinc.ejb.jpa.entities.dms3.CashReceiptsAdjust.class),
         @EntityResult(entityClass = com.jmiinc.ejb.jpa.entities.dms3.CashReceiptsApplict.class),
         @EntityResult(entityClass = com.jmiinc.ejb.jpa.entities.dms3.CashReceiptsCheck.class),
         @EntityResult(entityClass = com.jmiinc.ejb.jpa.entities.dms3.CashReceiptsDepo.class)} ,
      columns = {@ColumnResult(name = "bank_account_name"),
         @ColumnResult(name = "customer_number"),
         @ColumnResult(name = "customer_lookup_name"),
         @ColumnResult(name = "application_customer_number"),
         @ColumnResult(name = "invoice_number_sales"),
         @ColumnResult(name = "o1_orderno"),
         @ColumnResult(name = "company_number"),
         @ColumnResult(name = "account"),
         @ColumnResult(name = "account_name"),
         @ColumnResult(name = "facility_profit_center_number")
      })
   })


From the report generating class, the call to obtain the virtual object for the report:
Code:
// read all rows from cash receipts tables for this deposit into the cashReceiptsJournal value object.
cashReceiptsJournalList = cashReceiptsDeposImpl.retrieveCashReceiptsJournal(
   selectedCashReceiptsDeposList[loopCtl].getCashReceiptsDeposNtty().getCashReceiptsDeposSerial());


This calls a service implementor which, in this case, only returns the result of a call to an EjbDAO show below:
Code:
public List<CashReceiptsJournalVO> retrieveCashReceiptsJournal (Integer cashReceiptsDeposSerial) {
   SimpleStringBuilder sqlQuery = new SimpleStringBuilder(2048);
   sqlQuery.append("SELECT crj.*, cra.*, crc.*, crd.*, bh.bank_account_name, cb1.customer_number, cb1.customer_lookup_name");
   sqlQuery.append(", cb2.customer_number AS application_customer_number, ar.invoice_number_sales");
   sqlQuery.append(", ord.o1_orderno, co.company_number, ca.account, ca.account_name,  fpc.facility_profit_center_number ");
   sqlQuery.append(" FROM dms3.cash_receipts_adjust  crj ");
   sqlQuery.append(" FULL OUTER JOIN dms3.cash_receipts_applict cra USING (cash_receipts_applict_serial)");
   sqlQuery.append(" FULL OUTER JOIN dms3.cash_receipts_check crc ON (cra.cash_receipts_check_serial = crc.cash_receipts_check_serial)");
   sqlQuery.append(" FULL OUTER JOIN dms3.cash_receipts_depos crd ON (crc.cash_receipts_depos_serial = crd.cash_receipts_depos_serial)");
   sqlQuery.append(" LEFT OUTER JOIN dms3.company co USING (company_serial)");
   sqlQuery.append(" LEFT OUTER JOIN dms3.accounts_receivable ar ON (cra.accounts_receivable_serial = ar.accounts_receivable_serial)");
   sqlQuery.append(" LEFT OUTER JOIN dms2.orders_o1 ord ON (cra.orders_o1_serial = ord.orders_o1_serial)");
   sqlQuery.append(" LEFT OUTER JOIN dms3.chart_of_accounts ca ON (crj.chart_of_accounts_serial = ca.chart_of_accounts_serial)");
   sqlQuery.append(" LEFT OUTER JOIN dms3.facility_profit_ctr fpc ON (crj.facility_profit_ctr_serial = fpc.facility_profit_ctr_serial)");
   sqlQuery.append(" LEFT OUTER JOIN dms3.customer_base cb1 ON (crc.cash_history_check_customer = cb1.customer_base_serial)");
   sqlQuery.append(" LEFT OUTER JOIN dms3.customer_base cb2 ON (cra.application_customer = cb2.customer_base_serial)");
   sqlQuery.append(" JOIN dms3.bank_hdg bh USING (bank_hdg_serial)");
   sqlQuery.append(" WHERE crd.cash_receipts_depos_serial = ? ");
   sqlQuery.append(" ORDER BY ");
   sqlQuery.append(" crd.cash_receipts_depos_serial ASC,");
   sqlQuery.append(" crc.cash_receipts_check_serial ASC,  ");
   sqlQuery.append(" cra.cash_receipts_applict_serial ASC, ");
   sqlQuery.append(" crj.cash_receipts_adjust_serial ASC ");
   Query query = em.createNativeQuery(sqlQuery.toString(), "CashReceiptsJournal");
   query.setParameter(1, cashReceiptsDeposSerial);
   List<CashReceiptsJournalVO> cashReceiptsJournalList = new ArrayList<>();
   List<?> listResult = query.getResultList();


When the statement listResult = query.getR..., an examination of listResult shows that 12 rows have been returned, but all 4 entities are null. The remaining linked columns are all valued correctly. My conclusion is that if the first listed table does not have a row matching the other tables, all 4 entities are returned null, even though the join is a full join, which should return all rows from all tables joined in this manner. As stated above, directly executed SQL does, in fact, return 12 rows with the columns for the adjustments null but the remaining entities column's properly valued. Shouldn't Hibernate do the same? If not, what Hibernate rule am I violating to get this result?

As previously stated, in this specific situation, I can eliminate this problem by making the adjustment class the 4th in the JPA list of entities to be returned and changing the SQL build to reflect that. This only works, however, because I will always have 1 or more application rows for each payment and 1 or more payment rows for each deposit. A full outer join cannot always rely on that being the case, so I am not sure how to differently structure the query to produce the desired results.


Top
 Profile  
 
 Post subject: Re: full outer join fails when first table listed is null
PostPosted: Tue Mar 08, 2016 3:51 pm 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1628
Location: Romania
I would definitely break this query into multiple ones which, not only are smaller and easier to tune, but will most probably faster too.
A query with 3 FULL OUTER and many LEFT JOIN, not to maintain multiple ordering criteria is going to fetch a lot of data and the execution plan is going to be difficult to optimize.
As for the full outer join, it can have NULLs on both the left and the right side.

I also question the need for entities in this query. Isn't a DTO projection much more appropriate for a query like this one? For me, it looks more like a report query.

Note that you can also use the @FieldResult annotation to guide Hibernate about the mapping of columns to entity properties.


Top
 Profile  
 
 Post subject: Re: full outer join fails when first table listed is null
PostPosted: Tue Mar 08, 2016 5:26 pm 
Newbie

Joined: Tue Mar 08, 2016 11:05 am
Posts: 5
Actually, this query is in support of a report, one called the cash receipts journal. The results of the query will be mapped to a DTO, (we refer to them as virtual objects), which, together with a number of other user entered and pre-set parameters will be passed to Jasper for generation of the actual report. Virtually all of the columns in each of the entities, as well as the other table's individually retrieved columns, are mapped to the CashReceiptsJournalVO and passed to Jasper, so I am not sure what @FieldResults would do for me unless you are saying that that would cause the query, as written, to return the values correctly. The fact that nearly all of the columns from each of the 4 entities is being used is why these were mapped as entities rather than being mapped individually as column or field results. I agree with your statement: "As for the full outer join, it can have NULLs on both the left and the right side." and that certainly is the SQL standard, but Hibernate does not appear to be doing so in this case. I also agree that this is a very complicated query and we spent a lot of time in Postgres's SQL tool fine tuning it to be sure that Postgres's optimizer would follow the most efficient key paths to resolve it. Creating multiple queries and then manually merging them in the mapping of the DTO to be passed to Jasper would definitely not be more efficient either from a coding or an execution standpoint. Really, the basic question is why isn't Hibernate returning the correct result set when SQL is. Is it possible that the fact that this particular deposit has no adjustments, so all adjustment columns will be null, has confused Hibernate and caused it to return the other entities as null even though they are not? Originally, I thought I had missed something in my coding, but I am starting to think this is a bug in Hibernate. Is that correct, or am I still missing something?


Top
 Profile  
 
 Post subject: Re: full outer join fails when first table listed is null
PostPosted: Wed Mar 09, 2016 1:21 am 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1628
Location: Romania
Check that User Guide link that I sent you. You can list all the entity columns individually and assign them to a unique alias. Then, you use the @FieldResult to associate the unique alias to an entity property.

Otherwise, how would Hibernate know that "crj.*" belongs to "CashReceiptsAdjust", "cra.*" to "CashReceiptsApplict", etc?


Top
 Profile  
 
 Post subject: Re: full outer join fails when first table listed is null
PostPosted: Wed Mar 09, 2016 10:02 am 
Newbie

Joined: Tue Mar 08, 2016 11:05 am
Posts: 5
Hibernate knows that crj.* belongs to the entity cashReceiptsAdjust and that cra.* belongs to the entity cashReceiptsApplict
because that is how the SqlQueryMapping named CashReceiptsJournal defines it. My understanding is the the mapping specifies how the SQL in the DAO is to be mapped. The SQL first returns crj.* which the SQLResultSetMapping says goes to the entity
CashReceiptsAdjust.class then cra.* which the mapping says goes to the entity CashReceiptsApplict.class, etc. An examination of
the listResult object following the execution of the listResult = query.getResultList() statement reveals that that is exactly
what is happening. List result contains a list of 12 objects representing the 12 rows returned by the query. Each object
consists of an object[0] which is shown as a CashReceiptsAdjustBO item, an object[1], shown as a CashReceiptsApplictBO item, the two additional entities and then object(4] through object[13], consisting of the individually mapped columns
bh.bank_account_name through fpc.facility_profit_center_number. So, Hibernate is returning listResult in exactly the structure
expected. The problem is not that Hibernate can't figure out the mapping, it has done so correctly. The problem is that
Hibernate is not performing full outer joins correctly when the first entity in the SELECT list does not have a row that matches
any of the rows from the other tables. I thought that this may be because I had no adjustments in this deposit, so I added 2
adjustment lines to the transaction. The result was that the listResult table contained 12 objects as before but the 2 that had
adjustments for them were returned with all entities properly valued while the 10 that had no adjustments returned nulls in the
4 entities. Clearly, in this case, Hibernate is not correctly executing the SQL.

On the chance that mapping individual columns would resolve this problem, I changed the JPA entity's mapping as follows:
Code:
@SqlResultSetMapping(name = "CashReceiptsJournal", entities = {
      @EntityResult(entityClass = com.jmiinc.ejb.jpa.entities.dms3.CashReceiptsAdjust.class, fields= {
            @FieldResult(name="cashReceiptsAdjustSerial", column="cash_receipts_adjust_serial"),
            @FieldResult(name="cashReceiptsApplictSerial", column="cash_receipts_applict_serial"),
            @FieldResult(name="companySerial", column="company_serial"),
            @FieldResult(name="chartOfAccountsSerial", column="chart_of_accounts_serial"),
            @FieldResult(name="facilityProfitCtrSerial", column="facility_profit_ctr_serial"),
            @FieldResult(name="adjustmentReason", column="adjustment_reason"),
            @FieldResult(name="arAdjustmentAmount", column="ar_adjustment_amount"),
            @FieldResult(name="cashReceiptsDeposSerial", column="cash_receipts_depos_serial"),
            @FieldResult(name="cashReceiptsCheckSerial", column="cash_receipts_check_serial"),
            @FieldResult(name="optoLock", column="opto_lock"),
      }),
      @EntityResult(entityClass = com.jmiinc.ejb.jpa.entities.dms3.CashReceiptsApplict.class),
      @EntityResult(entityClass = com.jmiinc.ejb.jpa.entities.dms3.CashReceiptsCheck.class),
      @EntityResult(entityClass = com.jmiinc.ejb.jpa.entities.dms3.CashReceiptsDepo.class)} ,
   columns = {@ColumnResult(name = "bank_account_name"),
      @ColumnResult(name = "customer_number"),
      @ColumnResult(name = "customer_lookup_name"),
      @ColumnResult(name = "application_customer_number"),
      @ColumnResult(name = "invoice_number_sales"),
      @ColumnResult(name = "o1_orderno"),
      @ColumnResult(name = "company_number"),
      @ColumnResult(name = "account"),
      @ColumnResult(name = "account_name"),
      @ColumnResult(name = "facility_profit_center_number")
   }),
I then changed the SQL in the DAO as follows:
Code:
sqlQuery.append("SELECT crj.cash_receipts_adjust_serial AS cash_receipts_adjust_serial ");
sqlQuery.append(", crj.cash_receipts_applict_serial AS cash_receipts_applict_serial");
sqlQuery.append(", crj.company_serial AS company_serial ");
sqlQuery.append(", crj.chart_of_accounts_serial AS chart_of_accounts_serial ");
sqlQuery.append(", crj.facility_profit_ctr_serial AS facility_profit_ctr_serial ");
sqlQuery.append(", crj.adjustment_reason AS adjustment_reason ");
sqlQuery.append(", crj.ar_adjustment_amount AS ar_adjustment_amount");
sqlQuery.append(", crj.cash_receipts_depos_serial AS cash_receipts_depos_serial ");
sqlQuery.append(", crj.cash_receipts_check_serial AS cash_receipts_check_serial ");
sqlQuery.append(", crj.opto_lock AS opto_lock, ");
sqlQuery.append("cra.*, crc.*, crd.*, bh.bank_account_name, cb1.customer_number, cb1.customer_lookup_name");
sqlQuery.append(", cb2.customer_number AS application_customer_number, ar.invoice_number_sales");
sqlQuery.append(", ord.o1_orderno, co.company_number, ca.account, ca.account_name,  fpc.facility_profit_center_number ");
sqlQuery.append(" FROM dms3.cash_receipts_adjust  crj ");
sqlQuery.append(" FULL OUTER JOIN dms3.cash_receipts_applict cra USING (cash_receipts_applict_serial)");
sqlQuery.append(" FULL OUTER JOIN dms3.cash_receipts_check crc ON (cra.cash_receipts_check_serial = crc.cash_receipts_check_serial)");
sqlQuery.append(" FULL OUTER JOIN dms3.cash_receipts_depos crd ON (crc.cash_receipts_depos_serial = crd.cash_receipts_depos_serial)");
sqlQuery.append(" LEFT OUTER JOIN dms3.company co USING (company_serial)");
sqlQuery.append(" LEFT OUTER JOIN dms3.accounts_receivable ar ON (cra.accounts_receivable_serial = ar.accounts_receivable_serial)");
sqlQuery.append(" LEFT OUTER JOIN dms2.orders_o1 ord ON (cra.orders_o1_serial = ord.orders_o1_serial)");
sqlQuery.append(" LEFT OUTER JOIN dms3.chart_of_accounts ca ON (crj.chart_of_accounts_serial = ca.chart_of_accounts_serial)");
sqlQuery.append(" LEFT OUTER JOIN dms3.facility_profit_ctr fpc ON (crj.facility_profit_ctr_serial = fpc.facility_profit_ctr_serial)");
sqlQuery.append(" LEFT OUTER JOIN dms3.customer_base cb1 ON (crc.cash_history_check_customer = cb1.customer_base_serial)");
sqlQuery.append(" LEFT OUTER JOIN dms3.customer_base cb2 ON (cra.application_customer = cb2.customer_base_serial)");
sqlQuery.append(" JOIN dms3.bank_hdg bh USING (bank_hdg_serial)");
sqlQuery.append(" WHERE crd.cash_receipts_depos_serial = ? ");
sqlQuery.append(" ORDER BY ");
sqlQuery.append(" crd.cash_receipts_depos_serial ASC,");
sqlQuery.append(" crc.cash_receipts_check_serial ASC,  ");
sqlQuery.append(" cra.cash_receipts_applict_serial ASC, ");
sqlQuery.append(" crj.cash_receipts_adjust_serial ASC ");
Query query = em.createNativeQuery(sqlQuery.toString(), "CashReceiptsJournal");
query.setParameter(1, cashReceiptsDeposSerial);
List<CashReceiptsJournalVO> cashReceiptsJournalList = new ArrayList<>();
List<?> listResult = query.getResultList();

The result was exactly the same, those rows in which an adjustment existed returned correctly, those in which no adjustment existed returned null in all 4 entities.

To recap: There is a bug in Hibernate's full outer join. If there is no row in the first table listed, Hibernate will return null for all tables, missing the existence of rows in joined tables. If the first listed table has a row matching the other tables, the data is returned correctly. Clearly this is not compliant with SQL standards and renders the full outer join in Hibernate unreliable, as it will only work if the first table listed will have matching rows for all other joined tables. I do not know how, or even if, I can create a bug report for this, but clearly one need to be created.


Top
 Profile  
 
 Post subject: Re: full outer join fails when first table listed is null
PostPosted: Wed Mar 09, 2016 10:40 am 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1628
Location: Romania
Quote:
There is a bug in Hibernate's full outer join.


This is a native SQL query. Hibernate just takes it and passes it to the PreparedStatement.
You can activate the JDBC logging and check the SQL query that is sent to the database which should be the one that you supplied to the createNativeQuery.

So, how could a bug influence the outcome of a query that Hibernate doesn't auto-generate?

If you spot an issue, then it's best to write a test case to replicate it. You can use these templates which make it easy to submit a bug report. This is the next step you should do because this way you have something that I can also debug and reason on.


Top
 Profile  
 
 Post subject: Re: full outer join fails when first table listed is null
PostPosted: Wed Mar 09, 2016 7:21 pm 
Newbie

Joined: Tue Mar 08, 2016 11:05 am
Posts: 5
While I accept the fact that Hibernate is executing a native SQL, it isn't true that that is all it is doing. SQL will return a table with a column for each selected item, (each column from crj.*, each column from cra.*, etc.). Hibernate then does the mapping to put all of the crj.* columns into an entity, all of the cra.* columns in an entity, etc. I suspect, but don't really know, that it is in the mapping that the failure occurs.

I looked at you templates, but unfortunately I am restricted from downloading a new environment and don't really understand how to set them up. I have, however, created a simple test case that I hope you can use to replicate the error. The case contains 3 tables, testBank, testCust, and testOrder. The SQL to create and then populate these tables is shown below:
Code:
CREATE TABLE test_bank
(
   test_bank_serial serial NOT NULL,
   test_bank_id integer NOT NULL,
   test_bank_name character varying(40) NOT NULL,
   opto_lock integer DEFAULT 0,
   CONSTRAINT test_bank_key PRIMARY KEY (test_bank_serial)
)
WITH (
  OIDS = FALSE
);
CREATE UNIQUE INDEX test_bank_id_key
   ON test_bank (test_bank_id ASC NULLS LAST);
GRANT ALL ON TABLE test_bank TO postgres;
GRANT ALL ON TABLE test_bank TO general;
GRANT SELECT ON TABLE test_bank TO readonly;

CREATE TABLE test_cust
(
   test_cust_serial serial NOT NULL,
   test_cust_number integer NOT NULL,
   test_cust_name character varying NOT NULL,
   test_bank_serial integer NOT NULL,
   opto_lock integer DEFAULT 0,
   CONSTRAINT test_customer_key PRIMARY KEY (test_cust_serial)
)
WITH (
  OIDS = FALSE
);
CREATE UNIQUE INDEX test_cust_num_key
   ON test_cust (test_cust_number ASC NULLS LAST);
GRANT ALL ON TABLE test_cust TO postgres;
GRANT ALL ON TABLE test_cust TO general;
GRANT SELECT ON TABLE test_cust TO readonly;

CREATE TABLE test_order
(
   test_order_serial serial NOT NULL,
   test_order_number integer NOT NULL,
   test_cust_serial integer NOT NULL,
   test_order_amount numeric(11,2),
   opto_lock integer DEFAULT 0,
   CONSTRAINT test_order_key PRIMARY KEY (test_order_serial),
   CONSTRAINT test_order_cust FOREIGN KEY (test_cust_serial) REFERENCES test_cust (test_cust_serial) ON UPDATE NO ACTION ON DELETE RESTRICT
)
WITH (
  OIDS = FALSE
);
CREATE UNIQUE INDEX test_order_number_key
   ON test_order (test_order_number ASC NULLS LAST);
GRANT ALL ON TABLE test_order TO postgres;
GRANT ALL ON TABLE test_order TO general;
GRANT SELECT ON TABLE test_order TO readonly;


Code:
INSERT INTO test_bank (test_bank_id, test_bank_name, opto_lock) VALUES (1, 'Bank of America', 0);
INSERT INTO test_bank (test_bank_id, test_bank_name, opto_lock) VALUES (2, 'Commerce Bank', 0);
INSERT INTO test_cust (test_cust_number, test_cust_name, test_bank_serial, opto_lock) VALUES(1, 'CustNo 1',
  (SELECT test_bank_serial FROM test_bank WHERE test_bank_id = 1), 0);
INSERT INTO test_cust (test_cust_number, test_cust_name, test_bank_serial, opto_lock) VALUES(2, 'CustNo 2',
  (SELECT test_bank_serial FROM test_bank WHERE test_bank_id = 1), 0);
INSERT INTO test_cust (test_cust_number, test_cust_name, test_bank_serial, opto_lock) VALUES(3, 'CustNo 3',
  (SELECT test_bank_serial FROM test_bank WHERE test_bank_id = 2), 0);
INSERT INTO test_cust (test_cust_number, test_cust_name, test_bank_serial, opto_lock) VALUES(4, 'CustNo 4',
  (SELECT test_bank_serial FROM test_bank WHERE test_bank_id = 1), 0);
INSERT INTO test_order (test_order_number, test_cust_serial, test_order_amount, opto_lock) VALUES (1,
  (SELECT test_cust_serial FROM test_cust WHERE test_cust_number = 2), 100.00,0);
INSERT INTO test_order (test_order_number, test_cust_serial, test_order_amount, opto_lock) VALUES (2,
  (SELECT test_cust_serial FROM test_cust WHERE test_cust_number = 2), 200.00,0);
INSERT INTO test_order (test_order_number, test_cust_serial, test_order_amount, opto_lock) VALUES (3,
  (SELECT test_cust_serial FROM test_cust WHERE test_cust_number = 3), 300.00,0);


The SQL to retrieve all orders, customers and their bank names is:
Code:
SELECT o.*, c.*, b.test_bank_name
FROM test_order o
FULL OUTER JOIN test_cust c USING (test_cust_serial)
JOIN test_bank b USING (test_bank_serial)
ORDER BY test_cust_number ASC,
    test_order_number ASC;


Entities for these tables are below:
Code:
package com.jmiinc.ejb.jpa.entities.dms3;

import java.io.Serializable;
import javax.persistence.*;


/**
* The persistent class for the test_bank database table.
*
*/
@Entity
@Table(name="test_bank")
@NamedQuery(name="TestBank.findAll", query="SELECT t FROM TestBank t")
public class TestBank implements Serializable {
   private static final long serialVersionUID = 1L;

   @Id
   @SequenceGenerator(name="TEST_BANK_TESTBANKSERIAL_GENERATOR", sequenceName="TEST_BANK_TEST_BANK_SERIAL_SEQ")
   @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TEST_BANK_TESTBANKSERIAL_GENERATOR")
   @Column(name="test_bank_serial", unique=true, nullable=false)
   private Integer testBankSerial;

   @Column(name="opto_lock")
   private Integer optoLock;

   @Column(name="test_bank_id", nullable=false)
   private Integer testBankId;

   @Column(name="test_bank_name", nullable=false, length=40)
   private String testBankName;

   public TestBank() {
   }

   public Integer getTestBankSerial() {
      return this.testBankSerial;
   }

   public void setTestBankSerial(Integer testBankSerial) {
      this.testBankSerial = testBankSerial;
   }

   public Integer getOptoLock() {
      return this.optoLock;
   }

   public void setOptoLock(Integer optoLock) {
      this.optoLock = optoLock;
   }

   public Integer getTestBankId() {
      return this.testBankId;
   }

   public void setTestBankId(Integer testBankId) {
      this.testBankId = testBankId;
   }

   public String getTestBankName() {
      return this.testBankName;
   }

   public void setTestBankName(String testBankName) {
      this.testBankName = testBankName;
   }
}

Code:
package com.jmiinc.ejb.jpa.entities.dms3;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.ColumnResult;
import javax.persistence.Entity;
import javax.persistence.EntityResult;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.SequenceGenerator;
import javax.persistence.SqlResultSetMapping;
import javax.persistence.SqlResultSetMappings;
import javax.persistence.Table;


/**
* The persistent class for the test_cust database table.
*
*/
@Entity
@Table(name="test_cust")
@NamedQuery(name="TestCust.findAll", query="SELECT t FROM TestCust t")
@SqlResultSetMappings({
   @SqlResultSetMapping(name = "TestHibernateJoin", entities = {
         @EntityResult(entityClass = com.jmiinc.ejb.jpa.entities.dms3.TestOrder.class),
         @EntityResult(entityClass = com.jmiinc.ejb.jpa.entities.dms3.TestCust.class)} ,
      columns = {
         @ColumnResult(name = "test_bank_name")
      })
   })
public class TestCust implements Serializable {
   private static final long serialVersionUID = 1L;

   @Id
   @SequenceGenerator(name="TEST_CUST_TESTCUSTSERIAL_GENERATOR", sequenceName="TEST_CUST_TEST_CUST_SERIAL_SEQ")
   @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TEST_CUST_TESTCUSTSERIAL_GENERATOR")
   @Column(name="test_cust_serial", unique=true, nullable=false)
   private Integer testCustSerial;

   @Column(name="opto_lock")
   private Integer optoLock;

   @Column(name="test_bank_serial", nullable=false)
   private Integer testBankSerial;

   @Column(name="test_cust_name", nullable=false, length=2147483647)
   private String testCustName;

   @Column(name="test_cust_number", nullable=false)
   private Integer testCustNumber;

   public TestCust() {
   }

   public Integer getTestCustSerial() {
      return this.testCustSerial;
   }

   public void setTestCustSerial(Integer testCustSerial) {
      this.testCustSerial = testCustSerial;
   }

   public Integer getOptoLock() {
      return this.optoLock;
   }

   public void setOptoLock(Integer optoLock) {
      this.optoLock = optoLock;
   }

   public Integer getTestBankSerial() {
      return this.testBankSerial;
   }

   public void setTestBankSerial(Integer testBankSerial) {
      this.testBankSerial = testBankSerial;
   }

   public String getTestCustName() {
      return this.testCustName;
   }

   public void setTestCustName(String testCustName) {
      this.testCustName = testCustName;
   }

   public Integer getTestCustNumber() {
      return this.testCustNumber;
   }

   public void setTestCustNumber(Integer testCustNumber) {
      this.testCustNumber = testCustNumber;
   }

}

Code:
package com.jmiinc.ejb.jpa.entities.dms3;

import java.io.Serializable;
import java.math.BigDecimal;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;


/**
* The persistent class for the test_order database table.
*
*/
@Entity
@Table(name="test_order")
@NamedQuery(name="TestOrder.findAll", query="SELECT t FROM TestOrder t")
public class TestOrder implements Serializable {
   private static final long serialVersionUID = 1L;

   @Id
   @SequenceGenerator(name="TEST_ORDER_TESTORDERSERIAL_GENERATOR", sequenceName="TEST_ORDER_TEST_ORDER_SERIAL_SEQ")
   @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TEST_ORDER_TESTORDERSERIAL_GENERATOR")
   @Column(name="test_order_serial", unique=true, nullable=false)
   private Integer testOrderSerial;

   @Column(name="opto_lock")
   private Integer optoLock;

   @Column(name="test_cust_serial", nullable=false)
   private Integer testCustSerial;

   @Column(name="test_order_amount", precision=11, scale=2)
   private BigDecimal testOrderAmount;

   @Column(name="test_order_number", nullable=false)
   private Integer testOrderNumber;

   public TestOrder() {
   }

   public Integer getTestOrderSerial() {
      return this.testOrderSerial;
   }

   public void setTestOrderSerial(Integer testOrderSerial) {
      this.testOrderSerial = testOrderSerial;
   }

   public Integer getOptoLock() {
      return this.optoLock;
   }

   public void setOptoLock(Integer optoLock) {
      this.optoLock = optoLock;
   }

   public Integer getTestCustSerial() {
      return this.testCustSerial;
   }

   public void setTestCustSerial(Integer testCustSerial) {
      this.testCustSerial = testCustSerial;
   }

   public BigDecimal getTestOrderAmount() {
      return this.testOrderAmount;
   }

   public void setTestOrderAmount(BigDecimal testOrderAmount) {
      this.testOrderAmount = testOrderAmount;
   }

   public Integer getTestOrderNumber() {
      return this.testOrderNumber;
   }

   public void setTestOrderNumber(Integer testOrderNumber) {
      this.testOrderNumber = testOrderNumber;
   }

}


Business objects - testBank:
Code:
package com.jmiinc.ejb.value.object.dms3;

import com.jmiinc.ejb.jpa.entities.dms3.TestBank;
import com.jmiinc.ejb.value.object.base.AbstractDomainObject;


public class TestBankBO extends AbstractDomainObject  {
   private static final long serialVersionUID = DEFAULT_SERIAL_VERSION_ID;
   private TestBank testBankNtty;

   public TestBankBO () {
      testBankNtty = new TestBank();
   }

   public TestBank getTestBankNtty() {
      return testBankNtty;
   }

   public void setTestBankNtty(TestBank testBankNtty) {
      this.testBankNtty = testBankNtty;
   }

}


testCust:
Code:
package com.jmiinc.ejb.value.object.dms3;

import com.jmiinc.ejb.jpa.entities.dms3.TestCust;
import com.jmiinc.ejb.value.object.base.AbstractDomainObject;


public class TestCustBO extends AbstractDomainObject  {
   private static final long serialVersionUID = DEFAULT_SERIAL_VERSION_ID;
   private TestCust testCustNtty;

   public TestCustBO () {
      testCustNtty = new TestCust();
   }

   public TestCust getTestCustNtty() {
      return testCustNtty;
   }

   public void setTestCustNtty(TestCust testCustNtty) {
      this.testCustNtty = testCustNtty;
   }

}


testOrder
Code:
package com.jmiinc.ejb.value.object.dms3;

import com.jmiinc.ejb.jpa.entities.dms3.TestOrder;
import com.jmiinc.ejb.value.object.base.AbstractDomainObject;


public class TestOrderBO extends AbstractDomainObject  {
   private static final long serialVersionUID = DEFAULT_SERIAL_VERSION_ID;
   private TestOrder testOrderNtty;

   public TestOrderBO () {
      testOrderNtty = new TestOrder();
   }

   public TestOrder getTestOrderNtty() {
      return testOrderNtty;
   }

   public void setTestOrderNtty(TestOrder testOrderNtty) {
      this.testOrderNtty = testOrderNtty;
   }

}


EjbDAO for testCust:
Code:
package com.jmiinc.ejb.implementors.dms3;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import com.jmiinc.commonejb.constants.StaticReferenceInterface;
import com.jmiinc.commonejb.util.SimpleStringBuilder;
import com.jmiinc.ejb.implementors.base.GenericEjbDAO;
import com.jmiinc.ejb.jpa.entities.dms3.TestCust;
import com.jmiinc.ejb.jpa.entities.dms3.TestOrder;
import com.jmiinc.ejb.value.object.base.TestCustVO;
import com.jmiinc.ejb.value.object.dms3.TestCustBO;
import com.jmiinc.ejb.value.object.dms3.TestOrderBO;


@Stateless
@LocalBean
public class TestCustEjbDAO extends GenericEjbDAO<TestCust> implements StaticReferenceInterface {
   @PersistenceContext(unitName = "dms3-persistence-unit")
   private EntityManager em;
   public TestCustEjbDAO() {
      super(TestCust.class);
   }

   public List<TestCustVO> retrieveTestCust () {
      SimpleStringBuilder sqlQuery = new SimpleStringBuilder(1024);
      sqlQuery.append("SELECT o.*, c.*, b.test_bank_name");
      sqlQuery.append(" FROM dms3.test_order o ");
      sqlQuery.append(" FULL OUTER JOIN dms3.test_cust c USING (test_cust_serial)");
      sqlQuery.append(" JOIN dms3.test_bank b USING (test_bank_serial)");
      sqlQuery.append(" ORDER BY ");
      sqlQuery.append(" c.test_cust_number ASC,");
      sqlQuery.append(" o.test_order_number ASC  ");
      Query query = em.createNativeQuery(sqlQuery.toString(), "TestHibernateJoin");
      List<TestCustVO> testCustList = new ArrayList<>();
      List<?> listResult = query.getResultList();
      if (listResult.size() == 0) {
         return testCustList;
      } else {
         for (Iterator<?> iter = listResult.iterator(); iter.hasNext();) {
            Object[] objects = (Object[]) iter.next();
            TestCustVO testCustVObj = new TestCustVO();
            TestOrderBO testOrder = new TestOrderBO();
            testOrder.setTestOrderNtty((TestOrder) objects[0]);
            testCustVObj.setTestOrderObj(testOrder);
            TestCustBO testCustObj = new TestCustBO();
            testCustObj.setTestCustNtty((TestCust) objects[1]);
            testCustVObj.setTestCustObj(testCustObj);
            testCustVObj.setTestBankName((String) objects[2]);

            testCustList.add(testCustVObj);
         }
      }
      return testCustList;
   }

}


Execution of the following statement in any driver program will return the results shown.
Code:
      // Retrieve all records from testOrder, testCust saving them in testCustVO
      setTestCustList(
            testCustEjb.retrieveTestCust());


OrderNo OrderAmount CustNum CustName BankName
Bank of America
1 100 2 CustNo 2 Bank of America
2 200 2 CustNo 2 Bank of America
3 300 3 CustNo 3 CommerceBank
Bank of America

Note that 1st and 5th row's customer number and name are null, while they should be 1, CustNo 1 and 5, CustNo 5
Execution of the attached SQL will correctly return this data. Hopefully this gives you enough to replicate the error in your templates without too much difficulty.


Top
 Profile  
 
 Post subject: Re: full outer join fails when first table listed is null
PostPosted: Thu Mar 10, 2016 2:20 am 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1628
Location: Romania
I still need a test case with an assert that fails on a condition that you know it should be true but it's not.
Writing such a test is fairly easy, you just need to read that blog post I provided you.

There's no don't need for any configuration, the template takes care of that for you. The schema is generated from the mappings, and the tests are run with H2.
I'll take a look on it when it's done.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 10 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.