Problemas de performance com JPA e toplink [RESOLVIDO]

2 respostas
Dieval_Guizelini

Senhores,

tenho um programa que lê um arquivo texto (tamanho médio 20Mb), que faz um parse desse arquivo e insere os dados em um banco mysql (com todas as tabelas no formato myisam e a maioria com registro de tamanho fixo).

O procedimento de carga leva em média 100ms.

O parse leva em média 120ms.

A carga do banco está levando em média 26 minutos.
Sendo que se eu gerar um arquivo com os comandos insert e roda no console (mysql) a carga não leva dois minutos :evil:

a unidade de persistência, os controladores e as classe de entidades foram todas criadas automaticamente pelo netbeans.

Exemplos:

persistence.xml (removi as demais referências de class para simplificar)
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="GenbankFile2DBPU" transaction-type="RESOURCE_LOCAL">
    <provider>oracle.toplink.essentials.PersistenceProvider</provider>
    <class>br.ufpr.bioinfo.genbank.db.Anotacao</class>
    <class>br.ufpr.bioinfo.genbank.db.Genes</class>
    <properties>
      <property name="toplink.jdbc.user" value="genomas"/>
      <property name="toplink.jdbc.password" value="*****"/>
      <property name="toplink.jdbc.url" value="jdbc:mysql://localhost:3306/genomas"/>
      <property name="toplink.jdbc.driver" value="com.mysql.jdbc.Driver"/>
    </properties>
  </persistence-unit>
</persistence>
Um exemplo de uma classe Entity:
@Entity
@Table(name = "genes", catalog = "genomas", schema = "")
@NamedQueries({@NamedQuery(name = "Genes.findAll", query = "SELECT g FROM Genes g"), @NamedQuery(name = "Genes.findById", query = "SELECT g FROM Genes g WHERE g.id = :id"), @NamedQuery(name = "Genes.findByNome", query = "SELECT g FROM Genes g WHERE upper(g.nome) = upper(:nome)"), @NamedQuery(name = "Genes.findByLocusTag", query = "SELECT g FROM Genes g WHERE g.locusTag = :locusTag")})
public class Genes implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "id", nullable = false)
    private Integer id;
    @Basic(optional = false)
    @Column(name = "nome", nullable = false, length = 200)
    private String nome;
    @Basic(optional = false)
    @Column(name = "locus_tag", nullable = false, length = 50)
    private String locusTag;

    public Genes() {
    }
    // métodos gets/sets/equals e hash omitidos
}
E um exemplo do controlador:
public class GenesJpaController {

    public GenesJpaController() {
        emf = Persistence.createEntityManagerFactory("GenbankFile2DBPU");
    }
    private EntityManagerFactory emf = null;

    public EntityManager getEntityManager() {
        return emf.createEntityManager();
    }

    public void create(Genes genes) throws PreexistingEntityException, Exception {
        EntityManager em = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();
            em.persist(genes);
            em.getTransaction().commit();
        } catch (Exception ex) {
            if (findGenes(genes.getId()) != null) {
                throw new PreexistingEntityException("Genes " + genes + " already exists.", ex);
            }
            throw ex;
        } finally {
            if (em != null) {
                em.close();
            }
        }
    }
   // demais métodos omitidos por não estarem sendo utilizados...
}

Alguém tem alguma sugestão?

vw

Ps: versões:
Java 6u17
Netbeans: 6.7.1
Mysql: 5.1.36

2 Respostas

Andre_Brito

Primeiro, use um Profiler pra ver se é mesmo na parte de JPA. As vezes pode ser outra coisa (mas parece que é no JPA mesmo).

A primeira coisa que eu faria ao pegar esse código é jogar pra fora o getEntityManager no método create (não jogar fora… eu quis dizer jogar pra fora do try). Quando você chama o create, você persiste 1 de cada vez? Porque você poderia persistir em ‘lotes’ e depois dar o commit. Veja se você não está criando a GenesJpaController mais de uma vez (a criação da Factory é violentamente cara).

Tem como mostrar o código que chama o create?

Dieval_Guizelini

Opa André,

valeu pelas dicas,

mas o create está fora mesmo do laço.

A solução que encontrei e os passos que segui foram estes:

Escrevi todas as classes de persistência, usando JDBC mesmo, segue o modelo da classe de conexão:

public class Conexao {
    private static Logger log = Logger.getLogger(Conexao.class.getName());
    static {
        loadConfig();
    }

    private static class ThreadLocalConnection extends ThreadLocal {
        @Override
        public Object initialValue() {
            Connection con = null;
            try {
                Class.forName(DRIVER);
                con = DriverManager.getConnection(URL, USER, PWD);
                log.info("Conexao obtida");
            } catch (SQLException ex) {
                log.severe( ex.getMessage() );
            } catch (ClassNotFoundException ex) {
                log.severe(ex.getMessage());
            }
            return con;
        }
    }
    private static ThreadLocalConnection conn = new ThreadLocalConnection();

    public static Connection getConnection() {
        Connection con = (Connection) conn.get();
        if( con == null ) {
            log.severe("não foi possível conectar ao banco de dados em "+URL);
            System.exit(-1);
        }
        return con;
    }

    public static void loadConfig() {
        java.util.Properties prop = new java.util.Properties();
        java.io.File arq = new java.io.File("./banco.properties");
        if (!arq.exists()) {
            log.severe("banco.properties not found");
            return;
        }
        BufferedReader in = null;
        try {
            in = new BufferedReader(new FileReader(arq));
            prop.load(in);
            DRIVER = prop.getProperty("driver_class", DRIVER);
            URL = prop.getProperty("url", URL);
            USER = prop.getProperty("user", USER);
            PWD = prop.getProperty("password", PWD);
        } catch (FileNotFoundException ex) {
            log.severe(ex.getMessage());
        } catch (IOException ex) {
            log.severe(ex.getMessage());
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ex) {
                    Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }
    private static String DRIVER = "com.mysql.jdbc.Driver";
    private static String URL = "jdbc:mysql://localhost:3306/genomas";
    private static String USER = "genomas";
    private static String PWD = "*****";
}

Uma classe DAO...

public class TiposeqJpaController {

    private java.sql.Connection con = null;
    private java.sql.PreparedStatement psIns = null;
    private static Logger log = Logger.getLogger(TiposeqJpaController.class.getName());

    public TiposeqJpaController() {
        con = Conexao.getConnection();
        try {
            psIns = con.prepareStatement("insert into tiposeq (nome)" + " values (?)", PreparedStatement.RETURN_GENERATED_KEYS);
        } catch (SQLException ex) {
            log.warning(ex.getMessage());
        }
    }

    public void create(Tiposeq vo) {
        java.sql.PreparedStatement ps = null;
        java.sql.ResultSet res = null;
        try {
//            ps = con.prepareStatement("insert into tiposeq (nome)" +
//                    " values (?)",
//                    PreparedStatement.RETURN_GENERATED_KEYS);
            ps = psIns;
            ps.clearParameters();
            ps.setString(1, vo.getNome());
            int ret = ps.executeUpdate();
            if (ret != 1) {
                log.warning("failed to insert in tiposeq " + vo.getNome());
            }
            res = ps.getGeneratedKeys();
            if (res.next()) {
                vo.setId(res.getInt(1));
            }
        } catch (SQLException ex) {
            log.warning(ex.getMessage());
        } finally {
            //JDBCUtils.close(ps);
            JDBCUtils.close(res);
        }
    }

    public Tiposeq findTiposeq(Integer id) {
        java.sql.PreparedStatement ps = null;
        java.sql.ResultSet res = null;
        Tiposeq tipo = null;
        try {
            ps = con.prepareStatement("select id,nome from tiposeq " +
                    " where id = ?");
            ps.setInt(1, id);
            res = ps.executeQuery();
            if (res.next()) {
                tipo = new Tiposeq();
                tipo.setId(res.getInt(1));
                tipo.setNome(res.getString(2));
            }
        } catch (SQLException ex) {
            log.warning(ex.getMessage());
        } finally {
            JDBCUtils.close(ps);
            JDBCUtils.close(res);
        }
        return tipo;
    }

    public Tiposeq findTiposeqByNome(String nome) {
        java.sql.PreparedStatement ps = null;
        java.sql.ResultSet res = null;
        Tiposeq tipo = null;
        try {
            ps = con.prepareStatement("select id,nome from tiposeq " +
                    " where nome = ?");
            ps.setString(1, nome);
            res = ps.executeQuery();
            if (res.next()) {
                tipo = new Tiposeq();
                tipo.setId(res.getInt(1));
                tipo.setNome(res.getString(2));
            }
        } catch (SQLException ex) {
            log.warning(ex.getMessage());
        } finally {
            JDBCUtils.close(ps);
            JDBCUtils.close(res);
        }
        return tipo;
    }

    public java.util.List<Tiposeq> findTiposeqEntities() {
        java.sql.PreparedStatement ps = null;
        java.sql.ResultSet res = null;
        java.util.List<Tiposeq> tipos = new java.util.ArrayList<Tiposeq>();
        try {
            ps = con.prepareStatement("select id,nome from tiposeq ");
            res = ps.executeQuery();
            while (res.next()) {
                Tiposeq tipo = new Tiposeq();
                tipo.setId(res.getInt(1));
                tipo.setNome(res.getString(2));
                tipos.add(tipo);
            }
        } catch (SQLException ex) {
            log.warning(ex.getMessage());
        } finally {
            JDBCUtils.close(ps);
            JDBCUtils.close(res);
        }
        return tipos;
    }
}

As partes comentadas foram as otimizações que tentei...

Bom, com isso eu sai de 26min e fui para 23min em média.
Mas permitiu eu descobrir que o Driver JDBC que eu estava utilizando do MySQL (que vem junto com o netbeans 6.5) tem um bug, para campos char(1), aparentemente ele fazia substring(0,1)... atualizei o driver para a versão 5.1.10.

Ainda com problemas, removi o máximo de consultas que fazia, criando cache (hashmap) local das tabelas de pouco registros, quando o tipo desejado não é localiza, eu repito a pesquisa no banco, se achar eu atualizo o cache. O que provavelmente escondeu algum select criado pelo JPA que estava sendo executado sem o uso correto de índices no banco.

Fiz mais umas mudanças para persistir os objetivos que possuem listas de detalhes, usando ps.addBatch()... bom para resumir, estou com tempo de 1minuto e 36 segundos, bem melhor hehe.

Exemplo do addBatch() [url]http://www.hiteshagrawal.com/java/mysql-batch-insertupdate-in-java[/url]

vw pessoal.

Criado 8 de novembro de 2009
Ultima resposta 9 de nov. de 2009
Respostas 2
Participantes 2