martedì 26 agosto 2014

Creare eseguibili Windows con Java

Vai all'indice

Una delle necessità apparentemente banali, ma che possono avere una soluzione non immediata, è la creazione di programmi java eseguibili da un pc Windows.
La prima cosa da fare è aprire Eclipse e creare un nuovo Java project (File>New>Java project). All'interno della cartella src creo un pacchetto "prova" nel quale definisco il file Main.java e inserisco il codice:


package prova;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;


public class prova {

   private static StringBuilder oString = new StringBuilder(""); 
   

   public static void main(String[] args) {

       long start=System.currentTimeMillis();
       testString();
       long end=System.currentTimeMillis();
       oString.append("Tempo di esecuzione testString() "+(end-start)+" millis.\n");
       
       start=System.currentTimeMillis();
       testStringBuffer();
       end=System.currentTimeMillis();
       oString.append("Tempo di esecuzione testStringBuffer() "+(end-start)+" millis.\n");
       
       start=System.currentTimeMillis();
       testStringBuilder();
       end=System.currentTimeMillis();
       oString.append("Tempo di esecuzione testStringBuilder() "+(end-start)+" millis.\n");
  
       File file = new File("output.txt");
        try {
           FileWriter fw = new FileWriter(file);
           fw.write(oString.toString());
           fw.flush();
           fw.close();
        } catch (IOException e) {                        
           System.out.println(e);         
        }         
    }            

        private static void testString() {
          String x = "";
          for(int i=0;i<15000;i++){
            //operazione di append
            x+=i;
          }
        }
        
        private static void testStringBuffer() {
          StringBuffer x = new StringBuffer("");
          for(int i=0;i<15000;i++){
            //operazione di append
            x.append(i);
          }
        }
        
        private static void testStringBuilder() {
          StringBuilder x = new StringBuilder("");
          for(int i=0;i<15000;i++){
            //operazione di append
            x.append(i);
          }
        }        




   }



L'obiettivo è creare un eseguibile java, quindi è indifferente il codice da utilizzare nella classe Main. Ne approfitto per introdurre le classi StringBuilder e StringBuffer, molto utili per gestire stringhe di caratteri. Esse ereditano le caratteristiche dalla classe madre String, ma sono più performanti e permettono operazioni sulle stringhe più veloci e meno onerose in termini di utilizzo della memoria. L'unica classe Main del progetto definisce ed esegue tre metodi che ciclando su tre oggetti delle classi String, StringBuilder e StringBuffer ne mostrano le performance. Il risultato è salvato in un oggetto StringBuilder e scritto nel file di testo output.txt.

Per salvare il progetto come Jar eseguibile è sufficiente cliccare col tasto destro sull'icona di progetto nella finestra dei progetti, selezionare l'opzione Runnable Jar file e seguire la procedura guidata, inserendo il nome del progetto ed il path di destinazione come descrive l'immagine.



A questo punto il file Jar ottenuto può essere eseguito in diversi modi: da linea comando o tramite script .bat su Windows o .sh su Linux.
Io ho preferito incorporare l'archivio Jar in un file .exe utilizzando Launch4j, un programma scaricabile dal sito http://launch4j.sourceforge.net .
Launch4J è uno strumento cross-platform per la creazione di applicazioni eseguibili su Windows partendo da applicazioni Java distribuite come Jar. L'eseguibile deve essere configurato per essere elaborato con la versione JRE disponibile nel proprio pc. Launch4J fornisce anche un'interfaccia user friendly tramite la quale è possibile impostare le opzioni di runtime, come la dimensione iniziale, l'heap-size massimo ecc... E' anche possibile assegnare un'icona di applicazione.


Installato e aperto Launch4j appare un'interfaccia a schede. Nella scheda Basic è necessario inserire il path dove si trova il file jar da convertire ed il path dove andrà creato il file exe di destinazione. Nella scheda JRE impostare la versione utilizzata per il progetto. A questo punto compilare il progetto cliccando il tasto ingranaggio ed eseguirlo col tasto play, o in alternativa un doppio click sul file eseguibile appena creato. Questo è solo un esempio basilare, come potete notare l'interfaccia è ricca di schede e parametri.


Il risultato dell'elaborazione è la creazione del file di testo output.txt contenente i tempi di esecuzione delle tre classi. Solo per curiosità, è interessante notare che le classi StringBuffer e StringBuilder hanno tempi di esecuzione molto più bassi della classe String.


Tempo di esecuzione testString() 2576 millis.
Tempo di esecuzione testStringBuffer() 5 millis.
Tempo di esecuzione testStringBuilder() 4 millis.


Nel prossimo post: Creare eseguibili schedulati con Quartz.

Hai apprezzato questo post? Conferma le mie competenze o scrivi una segnalazione sul mio profilo Linkedin!

lunedì 18 agosto 2014

Hibernate e SQLite

Vai all'indice

Negli esempi precedenti per comodità ho utilizzato Apache Derby, tuttavia Hibernate funziona altrettanto bene con qualsiasi DB semplicemente modificando i parametri di configurazione e la dependency del file POM di Maven. Un caso interessante da trattare è sicuramente l'integrazione di Hibernate con Sqlite.



Molto spesso durante lo sviluppo di un progetto può nascere la necessità di gestire piccole quantità di dati, tali da non giustificare l’uso di un database. La soluzione sicuramente più immediata consiste nell’utilizzo di file di testo o XML come base di dati. Una soluzione alternativa, e a mio avviso più efficace, è nell’uso di SQLite.
SQLite è una piccola libreria  che implementa un vero e proprio motore di database le cui caratteristiche  sono:

  • la semplicità: nessun bisogno di installazione e configurazione;
  • la compattezza: un intero database è contenuto in un unico file, in questo modo è possibile inserire il file direttamente all'interno dell'applicazione stessa!
  • il supporto degli standard: SQLite implementa quasi completamente il linguaggio SQL;
  • la velocità;
  • il costo: GRATUITO.


Per la creazione del database ho scaricato SQLiteStudio, un semplice tool scaricabile dal sito http://sqlitestudio.pl e che non richiede installazione (esiste anche un plugin di Firefox che fa lo stesso lavoro).
Ho creato un nuovo database l'ho chiamato mydb, quindi ho definito la solita tabella "utente" secondo la struttura descritta dall'immagine sottostante.




A questo punto ho ottenuto un file mydb.db, che troviamo nella cartella dove ho posizionato SQLiteStudio e che contiene il DB appena definito.

Metto da parte il file contenente il db appena creato, apro Eclipse e creo un nuovo progetto Maven seguendo questo post.
Finora nel file POM ho inserito la dependency per il caricamento del driver Apache Derby, ora per il caricamento della libreria JDBC di SQLite definisco la dependency:


        <dependency>
            <groupId>org.xerial</groupId>
            <artifactId>sqlite-jdbc</artifactId>
            <version>3.7.2</version>
        </dependency>


Creo il file Utente.java all'interno della cartella jar e all'interno definisco l'entità Utente.


package jar;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "utente")
public class Utente {

@Id
private Integer userid;
private String nome;
private String cognome;

public Utente() {

}

public Utente(Integer userid, String nome, String cognome) {
this.userid = userid;
this.nome = nome;
this.cognome = cognome;
}

// Definisco getter e setter

}


Nella cartella resources definisco il file 'hibernate.cfg.xml'.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" 
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>
<property name="show_sql">true</property>
<property name="format_sql">true</property>
<property name="dialect">org.hibernate.dialect.SQLiteDialect</property>
<property name="connection.driver_class">org.sqlite.JDBC</property>
<property name="connection.url">jdbc:sqlite:mydb.db</property>
<property name="connection.username"></property>
<property name="connection.password"></property>

<property name="hibernate.hbm2ddl.auto">update</property>

<mapping class="jar.Utente"/>
</session-factory>
</hibernate-configuration>


Ho creato il pacchetto 'org.hibernate.dialect' nel progetto ed ho inserito il file SQLiteDialect, il file dialect è utilizzato da Hibernate per creare query SQL per lo specifico database, il file è scaricabile da diverse fonti nel web.

      /*
 * The author disclaims copyright to this source code.  In place of
 * a legal notice, here is a blessing:
 *
 *    May you do good and not evil.
 *    May you find forgiveness for yourself and forgive others.
 *    May you share freely, never taking more than you give.
 *
 */
package org.hibernate.dialect;

import java.sql.Types;

import org.hibernate.dialect.function.AbstractAnsiTrimEmulationFunction;
import org.hibernate.dialect.function.NoArgSQLFunction;
import org.hibernate.dialect.function.SQLFunction;
import org.hibernate.dialect.function.SQLFunctionTemplate;
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.dialect.function.VarArgsSQLFunction;
import org.hibernate.type.StandardBasicTypes;

public class SQLiteDialect extends Dialect {
  public SQLiteDialect() {
    registerColumnType(Types.BIT, "boolean");
    registerColumnType(Types.TINYINT, "tinyint");
    registerColumnType(Types.SMALLINT, "smallint");
    registerColumnType(Types.INTEGER, "integer");
    registerColumnType(Types.BIGINT, "bigint");
    registerColumnType(Types.FLOAT, "float");
    registerColumnType(Types.REAL, "real");
    registerColumnType(Types.DOUBLE, "double");
    registerColumnType(Types.NUMERIC, "numeric($p, $s)");
    registerColumnType(Types.DECIMAL, "decimal");
    registerColumnType(Types.CHAR, "char");
    registerColumnType(Types.VARCHAR, "varchar($l)");
    registerColumnType(Types.LONGVARCHAR, "longvarchar");
    registerColumnType(Types.DATE, "date");
    registerColumnType(Types.TIME, "time");
    registerColumnType(Types.TIMESTAMP, "datetime");
    registerColumnType(Types.BINARY, "blob");
    registerColumnType(Types.VARBINARY, "blob");
    registerColumnType(Types.LONGVARBINARY, "blob");
    registerColumnType(Types.BLOB, "blob");
    registerColumnType(Types.CLOB, "clob");
    registerColumnType(Types.BOOLEAN, "boolean");

    //registerFunction( "abs", new StandardSQLFunction("abs") );
    registerFunction( "concat", new VarArgsSQLFunction(StandardBasicTypes.STRING, "", "||", "") );
    //registerFunction( "length", new StandardSQLFunction("length", StandardBasicTypes.LONG) );
    //registerFunction( "lower", new StandardSQLFunction("lower") );
    registerFunction( "mod", new SQLFunctionTemplate(StandardBasicTypes.INTEGER, "?1 % ?2" ) );
    registerFunction( "quote", new StandardSQLFunction("quote", StandardBasicTypes.STRING) );
    registerFunction( "random", new NoArgSQLFunction("random", StandardBasicTypes.INTEGER) );
    registerFunction( "round", new StandardSQLFunction("round") );
    registerFunction( "substr", new StandardSQLFunction("substr", StandardBasicTypes.STRING) );
    registerFunction( "substring", new SQLFunctionTemplate( StandardBasicTypes.STRING, "substr(?1, ?2, ?3)" ) );
    registerFunction( "trim", new AbstractAnsiTrimEmulationFunction() {
        protected SQLFunction resolveBothSpaceTrimFunction() {
          return new SQLFunctionTemplate(StandardBasicTypes.STRING, "trim(?1)");
        }

        protected SQLFunction resolveBothSpaceTrimFromFunction() {
          return new SQLFunctionTemplate(StandardBasicTypes.STRING, "trim(?2)");
        }

        protected SQLFunction resolveLeadingSpaceTrimFunction() {
          return new SQLFunctionTemplate(StandardBasicTypes.STRING, "ltrim(?1)");
        }

        protected SQLFunction resolveTrailingSpaceTrimFunction() {
          return new SQLFunctionTemplate(StandardBasicTypes.STRING, "rtrim(?1)");
        }

        protected SQLFunction resolveBothTrimFunction() {
          return new SQLFunctionTemplate(StandardBasicTypes.STRING, "trim(?1, ?2)");
        }

        protected SQLFunction resolveLeadingTrimFunction() {
          return new SQLFunctionTemplate(StandardBasicTypes.STRING, "ltrim(?1, ?2)");
        }

        protected SQLFunction resolveTrailingTrimFunction() {
          return new SQLFunctionTemplate(StandardBasicTypes.STRING, "rtrim(?1, ?2)");
        }
    } );
    //registerFunction( "upper", new StandardSQLFunction("upper") );
  }

  public boolean supportsIdentityColumns() {
    return true;
  }

  /*
  public boolean supportsInsertSelectIdentity() {
    return true; // As specify in NHibernate dialect
  }
  */

  public boolean hasDataTypeInIdentityColumn() {
    return false; // As specify in NHibernate dialect
  }

  /*
  public String appendIdentitySelectToInsert(String insertString) {
    return new StringBuffer(insertString.length()+30). // As specify in NHibernate dialect
      append(insertString).
      append("; ").append(getIdentitySelectString()).
      toString();
  }
  */

  public String getIdentityColumnString() {
    // return "integer primary key autoincrement";
    return "integer";
  }

  public String getIdentitySelectString() {
    return "select last_insert_rowid()";
  }

  public boolean supportsLimit() {
    return true;
  }

  public boolean bindLimitParametersInReverseOrder() {
    return true;
  }

  protected String getLimitString(String query, boolean hasOffset) {
    return new StringBuffer(query.length()+20).
      append(query).
      append(hasOffset ? " limit ? offset ?" : " limit ?").
      toString();
  }

  public boolean supportsTemporaryTables() {
    return true;
  }

  public String getCreateTemporaryTableString() {
    return "create temporary table if not exists";
  }

  public boolean dropTemporaryTableAfterUse() {
    return true; // TODO Validate
  }

  public boolean supportsCurrentTimestampSelection() {
    return true;
  }

  public boolean isCurrentTimestampSelectStringCallable() {
    return false;
  }

  public String getCurrentTimestampSelectString() {
    return "select current_timestamp";
  }

  public boolean supportsUnionAll() {
    return true;
  }

  public boolean hasAlterTable() {
    return false; // As specify in NHibernate dialect
  }

  public boolean dropConstraints() {
    return false;
  }

  /*
  public String getAddColumnString() {
    return "add column";
  }
  */

  public String getForUpdateString() {
    return "";
  }

  public boolean supportsOuterJoinForUpdate() {
    return false;
  }

  public String getDropForeignKeyString() {
    throw new UnsupportedOperationException("No drop foreign key syntax supported by SQLiteDialect");
  }

  public String getAddForeignKeyConstraintString(String constraintName,
      String[] foreignKey, String referencedTable, String[] primaryKey,
      boolean referencesPrimaryKey) {
    throw new UnsupportedOperationException("No add foreign key syntax supported by SQLiteDialect");
  }

  public String getAddPrimaryKeyConstraintString(String constraintName) {
    throw new UnsupportedOperationException("No add primary key syntax supported by SQLiteDialect");
  }

  public boolean supportsIfExistsBeforeTableName() {
    return true;
  }

  public boolean supportsCascadeDelete() {
    return true;
  }

  /* not case insensitive for unicode characters by default (ICU extension needed)
  public boolean supportsCaseInsensitiveLike() {
    return true;
  }
  */

  public boolean supportsTupleDistinctCounts() {
    return false;
  }

  public String getSelectGUIDString() {
    return "select hex(randomblob(16))";
  }
}


Ora creo la classe HibernateUtil che mi aiuta a creare la SessionFactory dal file di configurazione di Hibernate. Il SessionFactory viene utilizzato per ottenere le istanze delle sessioni che rappresentano i singoli processi.

package jar;

import java.util.Properties; import org.hibernate.SessionFactory;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import org.hibernate.HibernateException;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistryBuilder;

public class HibernateUtil {

  
  private static SessionFactory sessionFactory = null;  
  private static ServiceRegistry serviceRegistry = null;

    public static SessionFactory getSessionFactory() {

      try {  
        Configuration configuration = new Configuration();  
        configuration.configure();  
        
        Properties properties = configuration.getProperties();
      
        serviceRegistry = new ServiceRegistryBuilder().applySettings(properties).buildServiceRegistry();          
        sessionFactory = configuration.buildSessionFactory(serviceRegistry);  
        } catch (Throwable ex) {
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
      
      return sessionFactory; 
    }
}


A questo punto riprendo il file mydb.db creato in precedenza e lo inserisco direttamente all'interno dell'applicazione con un semplice copia-incolla sull'icona di progetto.

La classe Main.java esegue le operazioni di CRUD. Come visto in precedenza tramite questa classe è possibile eseguire il progetto.


package jar;

import java.sql.Date;
import java.util.List;
import java.util.Set;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
public class Main {

   @SuppressWarnings("unchecked")
   public static void main(String[] args) {
      SessionFactory sessionFactory = HibernateUtil.configureSessionFactory();
      Session session = null;
      Transaction tx=null;
      
      try {

         session = sessionFactory.openSession();
         tx = session.beginTransaction();

       // Ripulisco il database
         List<Utente> UtenteList = session.createQuery("from Utente").list();
         for (Utente Utex : UtenteList) {
            session.delete(Utex);
         }         
         
       // Verifico sia vuoto
         for (Utente Utente : UtenteList) {
            System.out.println("Utente Id: " + Utente.getUserid() + " | Name:"  + Utente.getNome() + " | Email:" + Utente.getCognome());
         }            

         // Creo le entità Utente che salverò nel database sqlite
         Utente myUtente = new Utente(11, "Pippo", "Pippo");
         Utente yourUtente = new Utente(12, "Pluto", "Pluto");
         Utente herUtente = new Utente(13, "Paperino", "Paperino");
         
         // Salvo nel database
         session.save(myUtente);
         session.save(yourUtente);
         session.save(herUtente);   
         
         UtenteList = session.createQuery("from Utente").list();         
         // Fetching sui dati salvati
         for (Utente Utente : UtenteList) {
            System.out.println("Utente Id: " + Utente.getUserid() + " | Name:"  + Utente.getNome() + " | Email:" + Utente.getCognome());
         }         
         
         // Aggiornamento
         myUtente.setNome("Gino");
         
         session.merge(myUtente);
         
         // Cancellazione    
         session.delete(herUtente);
         
         // Eseguo un'altra estrazione 
         UtenteList = session.createQuery("from Utente").list();
         
         for (Utente Utente : UtenteList) {
            System.out.println("$$$ Id: " + Utente.getUserid() + " | Name:"  + Utente.getNome() + " | Email:" + Utente.getCognome());
         }
         
         // Commit.
         session.flush();
         tx.commit();
   } catch (Exception ex) {
      ex.printStackTrace();
      
      // Rollback.
      tx.rollback();
   } finally{
      if(session != null) {
         session.close();
      }
   }         

   }
}


Di seguito riporto l'alberatura del progetto.





Hai apprezzato questo post? Conferma le mie competenze o scrivi una segnalazione sul mio profilo Linkedin!

lunedì 11 agosto 2014

Query Hibernate con Criteria

Con Hibernate l'interazione tra applicazione e database avviene tramite HQL e JPQL, linguaggi ad hoc per astrarre la logica relazionale e lavorare direttamente ad oggetti. Tuttavia esiste un metodo alternativo di manipolare e interrogare i dati delle tabelle. Criteria API consente di creare un oggetto con dei criteri di programmazione in cui è possibile applicare delle regole di filtraggio e condizioni logiche.

L'interfaccia Session fornisce il metodo createCriteria(), il quale crea un oggetto Criteria che restituisce istanze della classe dell'oggetto in persistenza.
Di seguito un semplice esempio di una query criteria che restituisce un oggetto corrispondente alla classe Utente.

Criteria cr = session.createCriteria(Utente.class);
List results = cr.list();

E' possibile usare il metodo add() per aggiungere restrizioni alle query con criteria. Supponendo di disporre di una tabella "utente" contenente un campo "eta", l'esempio di seguito aggiunge una restrizione che restituisce gli utenti con un'età superiore ai 30 anni.

Criteria cr = session.createCriteria(Utente.class);
cr.add(Restrictions.eq("eta", 30));
List results = cr.list();

Di seguito altri esempi di Restrictions.

Criteria cr = session.createCriteria(Utente.class);

// Restituisce utenti con età superiore ai 30 anni.
cr.add(Restrictions.gt("eta", 30));

// Restituisce utenti con età inferiore ai 30 anni.
cr.add(Restrictions.lt("eta", 30));

// Restituisce utenti col nome che inizia con Gian
cr.add(Restrictions.like("nome", "Gian%"));

// Restituisce utenti con età compresa tra i 20 e i 30 anni.
cr.add(Restrictions.between("eta", 20, 30));

// Verifica se il campo è nullo
cr.add(Restrictions.isNull("eta"));

// Verifica che il campo non sia vuoto
cr.add(Restrictions.isNotEmpty("eta"));


Con Criteria è possibile anche ordinare e aggregare i risultati.

Criteria cr = session.createCriteria(Utente.class);

// Ordina gli utenti per età discendente
cr.addOrder(Order.desc("eta"));

// Conta le righe totali.
cr.setProjection(Projections.rowCount());

// Restituisce l'età più alta.
cr.setProjection(Projections.max("eta"));

// Restituisce l'età media.
cr.setProjection(Projections.avg("eta"));



Hai apprezzato questo post? Conferma le mie competenze o scrivi una segnalazione sul mio profilo Linkedin!

lunedì 4 agosto 2014

L'auto-referenziazione in Hibernate

Vai all'indice

Un breve accenno merita la gestione dell'autoreferenziazione in Hibernate in particolare relazioni: uno-a-molti e molti-a-molti.
Una tabella è autoreferenziata quando è correlata a se stessa da due campi diversi. Utilizzando  un esempio specifico, sempre la tabella Utente (pensiamo agli utenti come impiegati di un ufficio) può includere l'ID di un manager responsabile di altri utenti nella stessa tabella. Questa relazione è definita self-join o self-reference che è una sorta di struttura padre/figlio.


L'auto-referenziazione in Hibernate

Nella tabella Utente, è necessario definire una colonna MANAGER_ID mappata nella tabella stessa. Così per ogni dipendente verrà memorizzato id del proprio responsabile.

   ...
    @ManyToOne(cascade={CascadeType.ALL})
    @JoinColumn(name="manager_id")
    private Utente manager;

    @OneToMany(mappedBy="manager")
    private Set<Utente> subord = new HashSet<Utente>();
   ...

Nell'entità Utente sono state definiti due nuovi attributi: manager e subord che è un Set <Utente>. manager viene mappato con l'annotazione @ManyToOne mentre subord con @OneToMany. All'interno di @OneToMany è stato definito mappedBy = "manager" questo rende l'oggetto manager parte proprietaria dell'associazione.

Questa appena descrittà è un'associazione uno-a-molti, ma è possibile definire anche relazioni molti-a-molti autoreferenziate. Basti pensare ad una associazione del tipo utente e colleghi (anch'essi definiti come utenti). In questo caso però è necessario definire una tabella di collegamento come segue.


CREATE TABLE utente_collega (userid int NOT NULL, collid int NOT NULL,  PRIMARY KEY (userid, collid), CONSTRAINT FK_UFF FOREIGN KEY (userid) REFERENCES utente (userid),  CONSTRAINT FK_APP FOREIGN KEY (collid) REFERENCES applicazione (collid);


Nell'entità Utente vanno definite le seguenti annotazioni:

   ...
   @ManyToMany(cascade={CascadeType.ALL})
    @JoinTable(name="utentee_collega",
        joinColumns={@JoinColumn(name="userid")},
        inverseJoinColumns={@JoinColumn(name="collid")})
    private Set<Utente> collx = new HashSet<Utente>();

    @ManyToMany(mappedBy="collx")
    private Set<Utente> utex = new HashSet<Utente>();
   ...

Le annotazioni sono le stesse usate per definire una relazione molti a molti. In questo caso è necessario definire nell'entità Utente sia le annotazione della parte proprietaria che non proprietaria.



Hai apprezzato questo post? Conferma le mie competenze o scrivi una segnalazione sul mio profilo Linkedin!