Why does Hibernate JPA throw LazyInitialization exception when using getReference()?

I’m using Hibernate 5.2.11 as a JPA provider. I have an annotated class (PurchaseOrder) with another annotated class (Customer) as a field with a many-to-one relationship. BUT because the legacy code had the Customer and PO tables in different databases, I have a call to EntityManager’s getReference() in PurchaseOrder to return the Customer instance. I access these through a home-brewed class that uses Hibernate (details follow).
This is resulting in the following exception:

org.hibernate.LazyInitializationException: could not initialize proxy – no Session

I’ve been reading through the Hibernate, JPA and Java EE docs, but haven’t been able to puzzle out what I’m doing wrong. I know that Hibernate is using a Session behind the scenes to enable JPA capabilities, but when I access .getCustomer(), I’m creating a new EntityManager, so it should have the session it needs.

It’s plain that I’m missing a crucial bit of understanding, but I have no idea what it is. Can anyone enlighten me?

Here is the significant part of Purchase Order, which consists of Strings, Integers, Booleans and LocalDates (ie: they’re all @Basic fields), and Customer as the only contained class:

@Entity
@Table(name = "PurchaseOrder")
public class PurchaseOrder extends BaseEntity { // BaseEntity is a @mappedSuperclass containing the primary key info only.
...
    private Integer customerID;
    private Customer customer;
...
    @Column(name = "customerID")
    public Integer getCustomerID() {
        return customerID;
    }

    public void setCustomerID(Integer customerID) {
        this.customerID = customerID;
    }

    @Transient
    public Customer getCustomer() {
        LOG.info("Getting customer #{}", customerID);
        if (customerID != 0 && (customer == null || !customerID.equals(customer.getId()))) {
            customer = VdtsSysDB.getDB().get(Customer.class, customerID);
        }
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
        this.customerID = customer.getId();
    }
...

And this is Customer, which consists only of @Basic fields:

@Entity
@Table(name = "Customers")
public class Customer extends BaseEntity{
    private String custNo;
    private String businessName;
    private String contact;
...
    @Column(name = "custNo")
    public String getCustNo() {
        return custNo;
    }

    public void setCustNo(String custNo) {
        this.custNo = custNo;
    }

    @Column(name = "Name")
    public String getBusinessName() {
        return businessName;
    }

    public void setBusinessName(String businessName) {
        this.businessName = businessName;
    }

    @Column(name = "Contact")
    public String getContact() {
        return contact;
    }

    public void setContact(String contact) {
        this.contact = contact;
    }
    ...

This is my Hibernate utility class:

public class VdtsSysDB {
    private EntityManagerFactory entityManagerFactory;
    private static VdtsSysDB vdtsSysDB;

    public static VdtsSysDB getDB() {
        if (vdtsSysDB == null) {
            vdtsSysDB = new VdtsSysDB();
        }
        return vdtsSysDB;
    }

    private VdtsSysDB() {
        if (entityManagerFactory == null)
            entityManagerFactory = Persistence.createEntityManagerFactory("VDTS_SYSDB");
    }

    public EntityManager getEntityManager() {
        return entityManagerFactory.createEntityManager();
    }

    public void closeEntityManager(EntityManager entityManager) {
        try {
            entityManager.close();
        } catch (Exception e) {
            // Exception logging.
        }
    }
...
    /**
     * Issues an HQL Query and returns the results as a list.
     *
     * @param queryString - An HQL query.
     * @return A list of items representing the returned dataset.
     */
    public <T extends BaseEntity> List<T> query(String queryString) {
        List<T> results = null;
        LOG.info("Query: {}", queryString);
        EntityManager entityManager = null;
        try {
            entityManager = getEntityManager();
            results = entityManager.createQuery(queryString).getResultList();
            entityManager.close();
            LOG.info("Returned {} results.", results.size());
        } catch (Exception e) {
            if (entityManager != null && entityManager.isOpen()) entityManager.close();
            LOG.error("Unable to complete query {}.", queryString, e);
        }
        return results;
    }

    /**
     * Get an object from the database by specifying its class and its ID.
     * @param aClass the class type to be returned.
     * @param id the primary key to the item to be returned.
     * @param <T> the class type to be returned.
     * @return A single instance of the specified item of this class.
     */
    public <T extends BaseEntity> T get(Class aClass, Integer id) {
        LOG.info("Get #: {}, {}", id, aClass.getName());
        T result = null;
        EntityManager entityManager = null;
        try {
            entityManager = getEntityManager();
            Object object = entityManager.getReference(aClass, id);
            result = (T) object;
        } catch (Exception e) {
            LOG.error("Could not get {}", id, e);
        } finally {
            closeEntityManager(entityManager);
        }
        return result;
    }
}

The code which throws the exception is part of a JavaFX 8 application controller. initialize() is called on class load, while refreshPane() is called whenever the attached GUI is displayed. The exception is thrown on the call to Customer.getBusinessName().

@FXML
private TableView<PurchaseOrder> poTable;
@FXML
private TableColumn<PurchaseOrder, String> poNoCol;
@FXML
private TableColumn<PurchaseOrder, String> customerNameCol;
@FXML
private TableColumn<PurchaseOrder, LocalDate> orderDateCol;

@Override
protected void initialize() {
    super.initialize();
    poTable.getSelectionModel().selectedItemProperty().addListener(
            (observable, oldValue, newValue) -> {
                if (newValue != null) selectItem();
            });
    poNoCol.setCellValueFactory(new PropertyValueFactory<>("purchaseOrderNo"));
    customerNameCol.setCellValueFactory(param -> {
        PurchaseOrder po = param.getValue();
        Customer customer = po.getCustomer();
        String name = customer.getBusinessName(); /****** This is the line that throws the exception ******/
        StringProperty observableString = new SimpleStringProperty(name);
        return observableString;
            });
    orderDateCol.setCellValueFactory(new PropertyValueFactory<>("orderDate"));    
...
}

@Override
protected void refreshPane() {
    List<Customer> oList = VdtsSysDB.getDB().query("from Customer");
    customerCombo.setItems(FXCollections.observableList(oList));
    changeTable();
    clearWidgets();
    enableWidgets(false);
}

private void changeTable() {
    poTable.getSelectionModel().clearSelection();
    List<PurchaseOrder> oList = VdtsSysDB.getDB()
            .query("from PurchaseOrder where closed = " + (openRadio.isSelected() ? "0" : "1"));
    poTable.setItems(FXCollections.observableList(oList));
}
...

The full stack trace of the exception:

30-09-17 19:42:05.137 ERROR java.lang.Throwable - Exception in thread "JavaFX Application Thread" org.hibernate.LazyInitializationException: could not initialize proxy - no Session
30-09-17 19:42:05.137 ERROR java.lang.Throwable -   at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:146)
30-09-17 19:42:05.138 ERROR java.lang.Throwable -   at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:259)
30-09-17 19:42:05.139 ERROR java.lang.Throwable -   at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:73)
30-09-17 19:42:05.139 ERROR java.lang.Throwable -   at ca.vdts.buchanan.model.Customer_$$_jvst799_9.getBusinessName(Customer_$$_jvst799_9.java)
30-09-17 19:42:05.139 ERROR java.lang.Throwable -   at ca.vdts.buchanan.endtally.controllers.POController.lambda$initialize$1(POController.java:103)
30-09-17 19:42:05.140 ERROR java.lang.Throwable -   at javafx.scene.control.TableColumn.getCellObservableValue(TableColumn.java:578)
30-09-17 19:42:05.140 ERROR java.lang.Throwable -   at javafx.scene.control.TableColumn.getCellObservableValue(TableColumn.java:563)
30-09-17 19:42:05.140 ERROR java.lang.Throwable -   at javafx.scene.control.TableCell.updateItem(TableCell.java:644)
30-09-17 19:42:05.141 ERROR java.lang.Throwable -   at javafx.scene.control.TableCell.indexChanged(TableCell.java:468)
30-09-17 19:42:05.141 ERROR java.lang.Throwable -   at javafx.scene.control.IndexedCell.updateIndex(IndexedCell.java:116)
30-09-17 19:42:05.141 ERROR java.lang.Throwable -   at com.sun.javafx.scene.control.skin.TableRowSkinBase.updateCells(TableRowSkinBase.java:533)
30-09-17 19:42:05.141 ERROR java.lang.Throwable -   at com.sun.javafx.scene.control.skin.TableRowSkinBase.init(TableRowSkinBase.java:147)
30-09-17 19:42:05.142 ERROR java.lang.Throwable -   at com.sun.javafx.scene.control.skin.TableRowSkin.<init>(TableRowSkin.java:64)
30-09-17 19:42:05.142 ERROR java.lang.Throwable -   at javafx.scene.control.TableRow.createDefaultSkin(TableRow.java:212)
30-09-17 19:42:05.142 ERROR java.lang.Throwable -   at javafx.scene.control.Control.impl_processCSS(Control.java:872)
30-09-17 19:42:05.142 ERROR java.lang.Throwable -   at javafx.scene.Node.processCSS(Node.java:9058)
30-09-17 19:42:05.143 ERROR java.lang.Throwable -   at javafx.scene.Node.applyCss(Node.java:9155)
30-09-17 19:42:05.143 ERROR java.lang.Throwable -   at com.sun.javafx.scene.control.skin.VirtualFlow.setCellIndex(VirtualFlow.java:1964)
30-09-17 19:42:05.143 ERROR java.lang.Throwable -   at com.sun.javafx.scene.control.skin.VirtualFlow.getCell(VirtualFlow.java:1797)
30-09-17 19:42:05.143 ERROR java.lang.Throwable -   at com.sun.javafx.scene.control.skin.VirtualFlow.getCellLength(VirtualFlow.java:1879)
30-09-17 19:42:05.144 ERROR java.lang.Throwable -   at com.sun.javafx.scene.control.skin.VirtualFlow.computeViewportOffset(VirtualFlow.java:2528)
30-09-17 19:42:05.144 ERROR java.lang.Throwable -   at com.sun.javafx.scene.control.skin.VirtualFlow.layoutChildren(VirtualFlow.java:1189)
30-09-17 19:42:05.144 ERROR java.lang.Throwable -   at javafx.scene.Parent.layout(Parent.java:1087)
30-09-17 19:42:05.144 ERROR java.lang.Throwable -   at javafx.scene.Parent.layout(Parent.java:1093)
30-09-17 19:42:05.145 ERROR java.lang.Throwable -   at javafx.scene.Parent.layout(Parent.java:1093)
30-09-17 19:42:05.145 ERROR java.lang.Throwable -   at javafx.scene.Parent.layout(Parent.java:1093)
30-09-17 19:42:05.145 ERROR java.lang.Throwable -   at javafx.scene.Parent.layout(Parent.java:1093)
30-09-17 19:42:05.145 ERROR java.lang.Throwable -   at javafx.scene.Parent.layout(Parent.java:1093)
30-09-17 19:42:05.146 ERROR java.lang.Throwable -   at javafx.scene.Parent.layout(Parent.java:1093)
30-09-17 19:42:05.146 ERROR java.lang.Throwable -   at javafx.scene.Parent.layout(Parent.java:1093)
30-09-17 19:42:05.146 ERROR java.lang.Throwable -   at javafx.scene.Parent.layout(Parent.java:1093)
30-09-17 19:42:05.147 ERROR java.lang.Throwable -   at javafx.scene.Scene.doLayoutPass(Scene.java:552)
30-09-17 19:42:05.147 ERROR java.lang.Throwable -   at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2397)
30-09-17 19:42:05.147 ERROR java.lang.Throwable -   at com.sun.javafx.tk.Toolkit.lambda$runPulse$30(Toolkit.java:355)
30-09-17 19:42:05.147 ERROR java.lang.Throwable -   at java.security.AccessController.doPrivileged(Native Method)
30-09-17 19:42:05.148 ERROR java.lang.Throwable -   at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:354)
30-09-17 19:42:05.148 ERROR java.lang.Throwable -   at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:381)
30-09-17 19:42:05.148 ERROR java.lang.Throwable -   at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:510)
30-09-17 19:42:05.148 ERROR java.lang.Throwable -   at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:490)
30-09-17 19:42:05.149 ERROR java.lang.Throwable -   at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$404(QuantumToolkit.java:319)
30-09-17 19:42:05.149 ERROR java.lang.Throwable -   at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
30-09-17 19:42:05.150 ERROR java.lang.Throwable -   at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
30-09-17 19:42:05.150 ERROR java.lang.Throwable -   at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
30-09-17 19:42:05.150 ERROR java.lang.Throwable -   at java.lang.Thread.run(Thread.java:745)