zilcuanu
zilcuanu

Reputation: 3715

Query on Hibernate dirty checking

I am reading Java Persistence with Hibernate 2nd Edition. I found the below quote from the book which I am not clear with.

Another issue to consider is dirty checking. Hibernate automatically detects state changes in order to synchronize the updated state with the database. It’s usually safe to return a different instance from the getter method than the instance passed by Hibernate to the setter. Hibernate compares them by value—not by object identity—to determine whether the attribute’s persistent state needs to be updated. For example, the following getter method doesn’t result in unnecessary SQL UPDATEs:

public String getFirstName(){
    return new String(firstName);
}

My question is why returning a new String object will not make unnecessary SQL UPDATEs and how is it different from returning just the firstName ?

When I tried running the getFirstName, I did not see any update query being fired.

Please let me know as I am not clear.

Below is the code:

package org.prashdeep.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;


@Entity
public class User {


    protected Long id;

    @Id
    @GeneratedValue()
    public Long getId() { // Optional but useful
        return id;
    }

    protected String firstName;

    protected String lastName;

    public void setId(Long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {

        return lastName;
    }

    public void setLastName(String lastName) {
        System.out.println("Called from the setter of lastname");
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                '}';
    }
}

SaveUserEntity.java

package org.prashdeep.executor;

import bitronix.tm.resource.jdbc.PoolingDataSource;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataBuilder;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Environment;
import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorBuilderImpl;
import org.hibernate.service.ServiceRegistry;
import org.prashdeep.common.DatabaseProduct;
import org.prashdeep.model.User;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.transaction.Status;
import javax.transaction.UserTransaction;
import java.util.List;


public class SaveUserEntity {

    protected Context context;
    protected PoolingDataSource dataSource;
    public static String DATASOURCE_NAME = "myDS";
    public DatabaseProduct databaseProduct;


    public UserTransaction getUserTransaction() {
        try {
            return (UserTransaction) context.lookup("java:comp/UserTransaction");
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    public void rollback() {
        UserTransaction tx = getUserTransaction();
        try {
            if (tx.getStatus() == Status.STATUS_ACTIVE ||
                    tx.getStatus() == Status.STATUS_MARKED_ROLLBACK)
                tx.rollback();
        } catch (Exception ex) {
            System.err.println("Rollback of transaction failed, trace follows!");
            ex.printStackTrace(System.err);
        }
    }

    protected SessionFactory createSessionFactory()  throws Exception{

        this.dataSource = new PoolingDataSource();
        dataSource.setUniqueName(DATASOURCE_NAME);
        dataSource.setMinPoolSize(1);
        dataSource.setMaxPoolSize(5);
        dataSource.setPreparedStatementCacheSize(10);
        dataSource.setIsolationLevel("READ_COMMITTED");
        dataSource.setClassName("org.h2.jdbcx.JdbcDataSource");
        dataSource.setAllowLocalTransactions(true);
        dataSource.getDriverProperties().put(
                "URL",
                "jdbc:h2:mem:test"
        );


        dataSource.getDriverProperties().put("user", "sa");
        context = new InitialContext();
        dataSource.init();


        /*
            This builder helps you create the immutable service registry with
            chained method calls.
         */
        StandardServiceRegistryBuilder serviceRegistryBuilder =
                new StandardServiceRegistryBuilder();

        /*
            Configure the services registry by applying settings.
         */
        serviceRegistryBuilder
                .applySetting("hibernate.connection.datasource", "myDS")
                .applySetting("hibernate.format_sql", "true")
                .applySetting("hibernate.show_sql", "true")
                .applySetting("hibernate.hbm2ddl.auto", "create-drop");

        // Enable JTA (this is a bit crude because Hibernate devs still believe that JTA is
        // used only in monstrous application servers and you'll never see this code).
        serviceRegistryBuilder.applySetting(
                Environment.TRANSACTION_COORDINATOR_STRATEGY,
                JtaTransactionCoordinatorBuilderImpl.class
        );
        ServiceRegistry serviceRegistry = serviceRegistryBuilder.build();

        /*
            You can only enter this configuration stage with an existing service registry.
         */
        MetadataSources metadataSources = new MetadataSources(serviceRegistry);

        /*
            Add your persistent classes to the (mapping) metadata sources.
         */
        metadataSources.addAnnotatedClass(
                org.prashdeep.model.User.class
        );

        // Add hbm.xml mapping files
        // metadataSources.addFile(...);

        // Read all hbm.xml mapping files from a JAR
        // metadataSources.addJar(...)

        MetadataBuilder metadataBuilder = metadataSources.getMetadataBuilder();

        Metadata metadata = metadataBuilder.build();


        SessionFactory sessionFactory = metadata.buildSessionFactory();

        return sessionFactory;
    }




   public static void main(String args[]) throws Exception {
       SaveUserEntity obj = new SaveUserEntity();

        SessionFactory sessionFactory = obj.createSessionFactory();
        try {
            {
                /*
                    Get access to the standard transaction API <code>UserTransaction</code> and
                    begin a transaction on this thread of execution.
                 */
                UserTransaction tx = obj.getUserTransaction();
                tx.begin();

                /*
                    Whenever you call <code>getCurrentSession()</code> in the same thread you get
                    the same <code>org.hibernate.Session</code>. It's bound automatically to the
                    ongoing transaction and is closed for you automatically when that transaction
                    commits or rolls back.
                 */
                Session session = sessionFactory.getCurrentSession();

                User user = new User();
                user.setFirstName("Pradeep");
                user.setLastName("Kumar");

                /*
                    The native Hibernate API is very similar to the standard Java Persistence API and most methods
                    have the same name.
                 */
                session.persist(user);



                /*
                    Hibernate synchronizes the session with the database and closes the "current"
                    session on commit of the bound transaction automatically.
                 */
               tx.commit();
            }

            {
                UserTransaction tx = obj.getUserTransaction();
                tx.begin();


                /*
                    A Hibernate criteria query is a type-safe programmatic way to express queries,
                    automatically translated into SQL.
                 */
                List<User> users =
                        sessionFactory.getCurrentSession().createCriteria(
                                User.class
                        ).list();
                // SELECT * from MESSAGE
                users.get(0).setFirstName("Praveen");

                System.out.println(users.get(0).getFirstName());


                tx.commit();
            }

        } finally {
            obj.rollback();
        }
    }
}

Upvotes: 1

Views: 2211

Answers (1)

geert3
geert3

Reputation: 7321

Suppose in the database, you have a record where firstName is FOO. At some point, this record is loaded as a persistent entity, so you now have a Java object of type User where getFirstName() returns a String "FOO".

When you use setFirstName("BAR"); to modify the persistent object, then the next time you commit this, it will result in an SQL UPDATE. This is because Hibernate will compare the "database state" with the "memory state", it will see that there is a difference (FOO has become BAR) and he will launch an SQL UPDATE. So far all standard.

Now the point that the Hibernate Manual was trying to make, is that this comparison is based on equals and not ==. In our example it will check if the String values FOO and BAR are equal (oldValue.equals(newValue), conceptually), not if the same String object is returned (oldValue == newValue).

Hence their example: you can return new String(firstName) which is a different String object, but will be equals just the same.

Hope that's helpful.

Upvotes: 2

Related Questions