Java / SqlServer transaction divided into several methods

3

I have a method that executes multiple Prepared Statements , only two of them in methods of other classes. In my method, I have the object connection , and if you want to start a transaction, I have to do:

connection.setAutoCommit(false); 
connection.commit();

and in the case of an exception, do rollback as well. But my method calls other methods with other connection objects, here is my question, how do I start the transaction?

My method:

 public void insereProduto(Produto produto, ProdutoDesc productDesc, ArrayList<Autor> autores) throws ClassNotFoundException, SQLException {

    DatabaseNET connection = new DatabaseNET();

    PreparedStatement sp = connection.getConnection().prepareStatement("INSERT INTO products(products_quantity, products_image,products_price,products_date_added,products_last_modified,products_date_available,products_weight,products_status,products_tax_class_id,manufacturers_id,products_ordered,editoras_id,coleccoes_id,tipoproduto_id,condicoes_id,products_code,specials_id,products_ano) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", PreparedStatement.RETURN_GENERATED_KEYS);

    sp.setFloat(1, 99998);
    sp.setString(2, produto.getProducts_image());
    sp.setFloat(3, produto.getProducts_price());
    sp.setDate(4, produto.getProducts_date_added());
    sp.setDate(5, produto.getProducts_last_modified());
    sp.setDate(6, produto.getProducts_date_available());
    sp.setFloat(7, produto.getProducts_weight());
    sp.setBoolean(8, produto.getProducts_status());
    sp.setInt(9, produto.getProducts_tax_class_id());
    sp.setInt(10, produto.getManufacturers_id());
    sp.setInt(11, produto.getProducts_ordered());
    sp.setInt(12, produto.getEditoras_id());
    sp.setInt(13, produto.getColeccoes_id());
    sp.setInt(14, produto.getTipoproduto_id());
    sp.setInt(15, produto.getCondicoes_id());
    sp.setString(16, produto.getProducts_code());
    sp.setInt(17, produto.getSpecials_id());
    sp.setInt(18, produto.getProducts_ano());

    sp.executeUpdate();

    ResultSet rs = sp.getGeneratedKeys();
    rs.next();

    int product_id = rs.getInt(1);
    int category_id = produto.getCategory_id();

    insereProdutoCategoria(product_id, category_id);

    productDesc.setProducts_id(product_id);
    productDesc.insereProdutoDesc(productDesc);

    for (Autor autor : autores) {
        Autor autor_novo_produto = new Autor().getAutorByX3Autor(autor.getName());

        ProdutoAutor produto_autor_novo = new ProdutoAutor(product_id, Integer.parseInt(autor_novo_produto.getCod()), "", "", 0);
        produto_autor_novo.insereProdutoAutor(produto_autor_novo);

    }

    System.err.println("INSERT => "+product_id);
}

My DatabaseNET method:

public class DatabaseNET {
private static final String host = "jdbc:mysql://******"; 
private static final String database ="****";
private static final String port = "3306";
private static final String user = "****"; 
private static final String password = "****"; 

public Connection getConnection() throws ClassNotFoundException, SQLException {  

    String connect = host+":"+port+"/"+database+"?user="+user+"&password="+password;

    DriverManager.registerDriver(new com.mysql.jdbc.Driver());

    try {
        Connection conn = DriverManager.getConnection(connect);
        return conn;
    } catch (SQLException ex) {
        Logger.getLogger(DatabaseX3.class.getName()).log(Level.SEVERE, null, ex);
    }

    return null;
}

}

To generate an execution I am inserting "ahahah" into the method:

 public void insereProdutoDesc(DatabaseNET connection, ProdutoDesc produto) throws ClassNotFoundException {

    try {
        PreparedStatement sp = connection.getConnection().prepareStatement("INSERT INTO products_description(products_id,language_id,products_name,products_description,products_url,products_viewed,products_autores_texto,products_autores_textofinal) VALUES(?,?,?,?,?,?,?,?)");
        sp.setInt(1, produto.getProducts_id());
        sp.setInt(2, produto.getLanguage_id());
        sp.setString(3, produto.getProducts_name());
        sp.setString(4, produto.getProducts_description().trim());
        sp.setString(5, produto.getProducts_url());
        sp.setString(6, "AHAHAHA");
        //sp.setInt(6, produto.getProducts_viewed());
        sp.setString(7, produto.getProducts_autores_texto());
        sp.setString(8, produto.getProducts_autores_textofinal());

        sp.executeUpdate();
    } catch (SQLException ex) {
        Logger.getLogger(ProdutoDesc.class.getName()).log(Level.SEVERE, null, ex);
    }

}
    
asked by anonymous 07.11.2014 / 10:22

1 answer

3

If you want the methods invoked to execute your SQL commands within the same transaction opened by the main method, they must use the same connection opened by the main method. You have to pass the connection as a parameter or draw some other way the methods share the existing connection.

We do not know your DatabaseNET class, and the exact solution depends on that. Here is a suggestion of a possible solution. Notice that things are missing there, like closing the connection.

I have explained changes in your code in comments in the code itself:

public void insereProduto(Produto produto, ProdutoDesc productDesc, ArrayList<Autor> autores) throws ClassNotFoundException, SQLException {

    DatabaseNET connection = new DatabaseNET();

    PreparedStatement sp = connection.getConnection().prepareStatement("INSERT INTO products(products_quantity, products_image,products_price,products_date_added,products_last_modified,products_date_available,products_weight,products_status,products_tax_class_id,manufacturers_id,products_ordered,editoras_id,coleccoes_id,tipoproduto_id,condicoes_id,products_code,specials_id,products_ano) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", PreparedStatement.RETURN_GENERATED_KEYS);

    sp.setFloat(1, 99998);
    sp.setString(2, produto.getProducts_image());
    sp.setFloat(3, produto.getProducts_price());
    sp.setDate(4, produto.getProducts_date_added());
    sp.setDate(5, produto.getProducts_last_modified());
    sp.setDate(6, produto.getProducts_date_available());
    sp.setFloat(7, produto.getProducts_weight());
    sp.setBoolean(8, produto.getProducts_status());
    sp.setInt(9, produto.getProducts_tax_class_id());
    sp.setInt(10, produto.getManufacturers_id());
    sp.setInt(11, produto.getProducts_ordered());
    sp.setInt(12, produto.getEditoras_id());
    sp.setInt(13, produto.getColeccoes_id());
    sp.setInt(14, produto.getTipoproduto_id());
    sp.setInt(15, produto.getCondicoes_id());
    sp.setString(16, produto.getProducts_code());
    sp.setInt(17, produto.getSpecials_id());
    sp.setInt(18, produto.getProducts_ano());

    // inicia um try..catch para poder fazer rollback em caso de exceção
    try {
        // Inicia a transação antes do primeiro comando SQL.
        // Pelo que entendi, a transação já será aberta implicitamente pelo SGBD
        // e o setAutoCommit(false) é para que ela mantenha-se 
        // aberta depois do primeiro comando.
        connection.setAutoCommit(false); 

        sp.executeUpdate();

        ResultSet rs = sp.getGeneratedKeys();
        rs.next();

        int product_id = rs.getInt(1);
        int category_id = produto.getCategory_id();

        // Se você quer que os comandos executados dentro deste método participem
        // da mesma transação, eles devem utilizar a mesma conexão, 
        // a qual já tem uma transação aberta
        insereProdutoCategoria(connection, product_id, category_id);

        productDesc.setProducts_id(product_id);
        productDesc.insereProdutoDesc(productDesc);

        for (Autor autor : autores) {
            Autor autor_novo_produto = new Autor().getAutorByX3Autor(autor.getName());

            ProdutoAutor produto_autor_novo = new ProdutoAutor(product_id, Integer.parseInt(autor_novo_produto.getCod()), "", "", 0);

            // Mais uma vez a conexão, com a transação já aberta, 
            // sendo passada por parâmetro
            produto_autor_novo.insereProdutoAutor(connection, produto_autor_novo);

    } catch (Exception e) {
        // Talvez você precise verificar se a conexão 
        // e a transação ainda estão abertas, isso depende da implementação de DatabaseNET
        if (connection.isOpen() && connection.isTransactionOpen()) {
            connection.rollback();
        }
        // Propaga a exceção para o chamador do método
        // saber que não funcionou.
        throw e;
    }
    // Se não houve exceção, commita
    connection.commit();
}

My intention here is to help only with the exact problem you raised, so I will not launch discussion on some things that can be improved on your design. We can discuss these in other questions if you are interested.

    
07.11.2014 / 16:50