1

I'm new to using JPA and trying to transition my code from JdbcTemplate to JPA. Originally I updated a subset of my columns by taking in a map of the columns with their values and created the SQL Update string myself and executed it using a DAO. I was wondering what would be the best way to do something similar using JPA?

EDIT:
How would I transform this code from my DAO to something equivalent in JPA?

public void updateFields(String userId, Map<String, String> fields) {
    StringBuilder sb = new StringBuilder();
    for (Entry<String, String> entry : fields.entrySet()) {
        sb.append(entry.getKey());
        sb.append("='");
        sb.append(StringEscapeUtils.escapeEcmaScript(entry.getValue()));
        sb.append("', ");
    }

    String str = sb.toString();
    if (str.length() > 2) {
        str = str.substring(0, str.length() - 2); // remove ", "
        String sql = "UPDATE users_table SET " + str + " WHERE user_id=?";
        jdbcTemplate.update(sql, new Object[] { userId },
                new int[] { Types.VARCHAR });
    }
}
Hank
  • 3,367
  • 10
  • 48
  • 86

3 Answers3

5

You have to read more about JPA for sure :)

Once entity is in Persistence Context it is tracked by JPA provider till the end of persistence context life or until EntityManager#detach() method is called. When transaction finishes (commit) - the state of managed entities in persistence context is synchronized with database and all changes are made.

If your entity is new, you can simply put it in the persistece context by invoking EntityManager#persist() method.

In your case (update of existing entity), you have to get a row from database and somehow change it to entity. It can be done in many ways, but the simpliest is to call EntityManager#find() method which will return managed entity. Returned object will be also put to current persistence context, so if there is an active transaction, you can change whatever property you like (not the primary key) and just finish transaction by invoking commit (or if this is container managed transaction just finish method).

update

After your comment I can see your point. I think you should redesign your app to fit JPA standards and capabilities. Anyway - if you already have a map of pairs <Attribute_name, Attrbute_value>, you can make use of something called Metamodel. Simple usage is shown below. This is naive implementation and works good only with basic attributes, you should take care of relationships etc. (access to more informations about attributes can be done via methods attr.getJavaType() or attr.getPersistentAttributeType())

Metamodel meta = entityManager.getMetamodel();
EntityType<User> user_ = meta.entity(User.class);

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaUpdate<User> update = cb.createCriteriaUpdate(User.class);

Root e = update.from(User.class);

for( Attribute<? super User, ?> attr : user_.getAttributes() ) {
      if (map.containsKey(attr.getName())) {
          update.set(attr, map.get(attr));
      }
}

update.where(cb.equal(e.get("id"), idOfUser));
entityManager.createQuery(update).executeUpdate();

Please note that Update Criteria Queries are available in JPA since 2.1 version.

Here you can find more informations about metamodel generation.

Alternatively to metamodel you can just use java reflection mechanisms.

Community
  • 1
  • 1
Maciej Dobrowolski
  • 11,561
  • 5
  • 45
  • 67
  • My entity will have up to 20 fields that could be updated. With the map, I don't know what fields may be updated, that is why I dynamically created the SQL Update statement. What could I do similarly with JPA? – Hank Jul 12 '14 at 21:56
1

JPA handles the update. Retrieve a dataset as entity using the entitymanager, change the value and call persist. This will store the changed data in your db.

Hannes
  • 2,018
  • 25
  • 32
0

In case you are using Hibernate(as JPA provider), here's an example

Entity

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

    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;

    @Column(name="NAME", nullable=false)
    private String name;

    other fields....
}

DAO

public interface PersonDao  {
    Person findById(int id);    
    void persist(Person person);

    ...
}

DaoImpl

@Repository("personDao")
public class PersonDaoImpl extends AnAbstractClassWithSessionFactory implements PersonDao {

    public Person findById(int id) {
        return (Person) getSession().get(Person.class, id);
    }

    public void persist(Person person){
       getSession().persist(person);
    }
}

Service

@Service("personService")
@Transactional
public class PersonServiceImpl implements PersonService {

    @Autowired
    PersonDao personDao;

    @Override
    public void createAndPersist(SomeSourceObject object) {
      //create Person object and populates with the source object
      Person person = new Person();     
      person.name = object.name;
      ...
      personDao.persist(person);
    }

    @Override
    public Person findById(int id) {
        return personDao.findById(id);
    }

    public void doSomethingWithPerson(Person person) {
         person.setName(person.getName()+" HELLO "); 
    //here since we are in transaction, no need to explicitly call update/merge
    //it will be updated in db as soon as the methods completed successfully
    //OR
    //changes will be undone if transaction failed/rolledback
   }
}

JPA documentation are indeed good resource for details.

From design point of view, if you have web interfacing, i tends to say include one more service delegate layer(PersonDelegateService e.g.) which maps the actual data received from UI to person entity (and viceversa, for display, to populate the view object from person entity) and delegate to service for actual person entity processing.

ba0708
  • 10,180
  • 13
  • 67
  • 99
Puneetsri
  • 234
  • 2
  • 7