JPA traps for the unwary

There are some JPA traps worthy of special examination. We will start by creating the following test case:

package com.gieman.tttracker.dao;

import com.gieman.tttracker.domain.Company;
import com.gieman.tttracker.domain.Project;
import com.gieman.tttracker.domain.User;
import static org.junit.Assert.assertTrue;
import org.junit.Test;

public class JpaTrapTest extends AbstractDaoForTesting {

    @Test
    public void testManyToOne() throws Exception {

        logger.debug("
STARTED testManyToOne()
");

        Company c = companyDao.findAll().get(0);
        Company c2 = companyDao.findAll().get(1);

        Project p = c.getProjects().get(0);

        p.setCompany(c2);
        p = projectDao.merge(p);

        assertTrue("Original company still has project in its collection!",
                !c.getProjects().contains(p));
        assertTrue("Newly assigned company does not have project in its collection",
                c2.getProjects().contains(p));

        logger.debug("
FINISHED testManyToOne()
");

    }

    @Test
    public void testFindByUsernamePassword() throws Exception {

        logger.debug("
STARTED testFindByUsernamePassword()
");

        // find by username/password combination
        User user = userDao.findByUsernamePassword("bjones", "admin");

        assertTrue("Unable to find valid user with correct username/password combination", 
                user != null);

        user = userDao.findByUsernamePassword("bjones", "ADMIN");

        assertTrue("User found with invalid password", 
                user == null); 
        
        logger.debug("
FINISHED testFindByUsernamePassword()
");
    }
}

Running this test case may surprise you:

JPA traps for the unwary

The first failure arises from the userDao.findByUsernamePassword statement, which uses the uppercase password:

user = userDao.findByUsernamePassword("bjones", "ADMIN");

Why was the user found with an obviously incorrect password? The reason is very simple and is a trap for the unwary developer. Most databases, by default, are case insensitive when matching text fields. In this situation the uppercase ADMIN will match the lowercase admin in the password field. Not exactly what we want when checking passwords! The database term that describes this behavior is collation; we need to modify the password column to use a case-sensitive collation. This can be achieved in MySQL with the following SQL command:

ALTER TABLE ttt_user MODIFY
    password VARCHAR(100)
      COLLATE latin1_general_cs;

Other databases will have similar semantics. This will change the collation on the password field to be case sensitive (note the _cs appended in latin1_general_cs). Running the test case will now result in expected behavior for case-sensitive password checking:

JPA traps for the unwary

The testManyToOne failure is another interesting case. In this test case, we are reassigning the project to a different Company. The p.setCompany(c2); line will change the assigned company to the second one in the list. We would expect that after calling the merge method on the project, the collection of projects in the c2 company would contain the newly reassigned project. In other words, the following code line should equate to true:

c2.getProjects().contains(p)

Likewise, the old company should no longer contain the newly reassigned project and hence should be false:

c.getProjects().contains(p)

This is obviously not the case and identifies a trap for developers new to JPA.

Although the persistence context understands the relationship between entities using @OneToMany and @ManyToOne, the Java representation of the relationship needs to be handled by the developer when collections are concerned. The simple changes required are as follows:

p.setCompany(c2);
p = projectDao.merge(p);

c.getProjects().remove(p);
c2.getProjects().add(p);

When the projectDao.merge(p) line is executed, the persistence context has no way of knowing the original parent company (if there is one at all; this may be a newly inserted project). The original Company entity in the persistence context still has a collection of projects assigned. This collection will never be updated during the lifetime of the Company entity within the persistence context. The additional two lines of code are used to remove the project (using remove) from the original company's project list and we add (using add) the project to the new company to ensure that the persistence context entities are updated to the correct state.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.213.212