0

I have the following model stored in a data base. Upon deletion of a room i receive a Referencial Integrity Constraint Violation, which I can't figure out.

An important rule for the model is: a person MUST have access to a room. If they lose all access (e.g. all rooms they have access to are deleted), they are removed from the database.

ER-Diagram

The ManyToMany Mapping in Question exists between Person and Room and exists in the form of the table "access". Ill be sharing some code for:

  • Person.java

  • Room.java

  • an excerpt from DAO.java (data access object)

  • DeleteRoom.java (main)

In order to uphold consistency in my data base i have come up with the following logic which i have tried to implement in DAO.deleteRoomWithConsistency(RoomId roomId), which I shared below:

When a room is deleted i run a loop over all persons who have access to that room:

enter image description here

Now the code:

Person.java:

@Entity(name = "Person")
@Table(name = "Person")
public class Person {

    public Person(int persNumber, String name, LocalDate dob) {
        super();
        this.persNumber = persNumber;
        this.name = name;
        this.dob = dob;
    }

    @Id
    @Column(name = "persNumber")
    private int persNumber;

    @Column(name = "name")
    private String name;

    @Column(name = "dob")
    private LocalDate dob;
    
    @ManyToOne(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST,CascadeType.REFRESH}, fetch=FetchType.LAZY)
    @JoinColumn(name="orgUnit")
    private OrgUnit orgUnit;

    public void setOrgUnit(OrgUnit orgUnit) {
        this.orgUnit = orgUnit;
    }

    // List of Rooms a Person has Access to
    // Owning Side of Bidirectional Relationship with Room
    @ManyToMany(fetch = FetchType.LAZY,
            cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST,CascadeType.REFRESH}
            )
    @JoinTable(name = "access", joinColumns = @JoinColumn(name = "person_id"), 
              inverseJoinColumns = {@JoinColumn(name = "building", referencedColumnName = "building"),
                                   @JoinColumn(name = "number", referencedColumnName = "number")})
    private Set<Room> roomAccess = new HashSet<>();

Room.java:

@Entity(name = "Room")
@Table(name = "Room")
public class Room {
    
    @EmbeddedId
    private RoomId roomId;

    @Column(name = "area")
    private int area;

    @Column(name = "usetype")
    @Enumerated(EnumType.STRING) //Adds the String Value of the Enum, instead of the ordinal value
    private UseType usetype;

    //List of Persons with Access to a Room
    //Inverse Side of Bidirectional Relationship with Person
    @ManyToMany(mappedBy = "roomAccess", cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST,CascadeType.REFRESH})
    private Set<Person> personsWithAccess = new HashSet<>();

DeleteRoom.java

public class DeleteRoom {

    public static void main(String[] args) throws SQLException {

        //Connection to the database 
        DAO dao = new DAO();
        dao.connect("./db/hw01", "bitub", "");

        //Inputs
        Building b1 = DAO.getInstance().readBuildingByName(
                "Beispielhaus"  //Enter the Building of the Room
                );
        Integer roomNumber = 102; //Enter Room Number
        
        RoomId toBeDeletedRoomId = new RoomId(b1, roomNumber);
        
        //Room r = DAO.getInstance().readRoom(toBeDeletedRoomId);
        //System.out.println(r);

        DAO.getInstance().deleteRoomWithConsistency(toBeDeletedRoomId);
        
        dao.closeConnection();
    }

}

excerpt from DAO.java

public class DAO {

    private static Connection conn;
    
    public void connect(String url, String username, String password) throws SQLException {

        String connectionURL = "jdbc:h2:" + url;

        try {
            conn = DriverManager.getConnection(connectionURL, username, password);

        } catch (SQLException e) {
            e.printStackTrace();
        }

        System.out.println("connecting to " +connectionURL);

    }

    /**
     * Close
     * 
     */
    public void closeConnection() throws SQLException {
        conn.close();
    }

/**
* deletes a room by their primary key with consistency
*/
    public void deleteRoomWithConsistency(RoomId roomId) throws SQLException {

        Session session = null;
        Transaction transaction = null;
        
        try {
            // open the session
            session = HibernateUtil.getSessionFactory().openSession();
            // begin the transaction
            transaction = session.beginTransaction();
            
            //retrieving room from the database
            Room deletedRoom = (Room) session.find(Room.class, roomId);
            
            //Set of people who have access to the room with roomId
            Set<Person> peopleWithAccess = this.readPersonsWithAccessForRoom(roomId);
            //keeping consistency
            //for all persons with access to the deleted room
            for (Person pWithAccess : peopleWithAccess) {
                
                //Set of rooms a pWithAccess has access to
                Set<Room> roomsWithAccess = this.readRoomsWithAccessForPerson(pWithAccess);
                //if 
                //this person only has access to one room (the deleted room), the person is deleted
                if (roomsWithAccess.size()==1) {
                    
                    //Set of People who are in the same org unit as pWithAccess
                    Set<Person> orgUnitMembers = this.readOrgUnitMembers(pWithAccess.getOrgUnit().getName());
                    //if 
                    //this person is the only person in the org unit, the org unit is deleted, by orphan removal the person is also deleted
                    if(orgUnitMembers.size()==1) {
                        
                        //remove org unit -> remove lonely member (has access to only one room and is the only member of his org unit)
                        session.delete(pWithAccess.getOrgUnit().getName());
                    //there are other people in the orgunit, he/she might be the head of the unit
                    }else{
                        
                        //Boolean if pWithAccess is the head of the unit
                        Boolean isHeadOfUnit = this.checkIfHeadOfUnit(pWithAccess);
                        //if
                        //he/she is head of the unit, a new head of the unit must be appointed
                        if(isHeadOfUnit==true) {
                            
                            Iterator<Person> it = orgUnitMembers.iterator();
                        
                            //appoint the first person, who isn't the old head of unit, the new head of unit
                            while (it.hasNext()) {
                                if (it.next() != pWithAccess) {
                                    pWithAccess.getOrgUnit().setHeadOfUnit(it.next());
                                    break;
                                }
                            }
                        }   
                        
                        //remove person (has access to only one room, but is not the only one in his org unit)
                        session.delete(pWithAccess);
                    }               
                //this person has access to multiple rooms, the room is deleted from the rooms this person has access to
                } else {
                    session.createSQLQuery(""
                            + "DELETE FROM access  "
                            + "WHERE person_id = '" + pWithAccess.getPersNumber() +"'"
                            + "AND building='" + roomId.getBuildingName()+ "' "
                            + "AND number ='" + roomId.getNumber()+ "' ");
                    //roomsWithAccess.remove(deletedRoom);
                }
            }
            
            //removing the room
            session.delete(deletedRoom);
            
            // commit the transaction
            transaction.commit();

        } catch (final HibernateException e) {
            rollbackTransactionIfPossible(transaction);
            e.printStackTrace();
        } catch (final Exception e) {
            rollbackTransactionIfPossible(transaction);
            e.printStackTrace();
        } finally {
            // close the session
            if ((session != null) && session.isOpen()) {
                session.close();
            }
        }
    }

}

And finally, when I run my main method to delete a room I get the following output:

Hibernate: select room0_.building as building4_5_0_, room0_.number as number1_5_0_, room0_.area as area2_5_0_, room0_.usetype as usetype3_5_0_ from Room room0_ where room0_.building=? and room0_.number=?
Hibernate: delete from Room where building=? and number=?
...
Referential integrity constraint violation: "FK3SHH9TEIQLTR6V1EAPFWO13WM: PUBLIC.ACCESS FOREIGN KEY(BUILDING, NUMBER) REFERENCES PUBLIC.ROOM(BUILDING, NUMBER) ('Beispielhaus', 102)"; SQL statement:
delete from Room where building=? and number=? [23503-214]
Dez. 29, 2022 10:04:38 NACHM. org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl release
INFO: HHH000010: On release of batch it still contained JDBC statements
...

I hope I could provide enough information for someone to help me out. Any insight would be greatly appreciated.

I have tried tinkering with the mapping, adding or removing the CascadeType.REMOVE. But in my research I learned that CascadeType.REMOVE or ALL shouldn't be used for "...ToMany" associations.

I have also tried debugging, but I didn't learn anything from that. The transaction builds up and when it is commited, the error happens.

Any help with this error is greatly appreciated.

elliot_mmm
  • 11
  • 2

0 Answers0