java - raspberry - ejecutar script al inicio linux centos 7
Deshaga A si B sale mal. arranque de primavera, jdbctemplate (6)
Tengo un método, ''databaseChanges'', que llama a 2 operaciones: A, B de forma iterativa. ''A'' primero, ''B'' último. ''A'' y ''B'' pueden ser funcionalidades de actualización, actualización y actualización en mi almacenamiento permanente, Oracle Database 11g.
Digamos,
''A'' actualiza un registro en la tabla Usuarios, atributo zip, donde id = 1.
''B'' inserta un registro en pasatiempos en la mesa.
Escenario: se llama al método databaseChanges, ''A'' opera y actualiza el registro. ''B'' funciona e intenta insertar un registro, sucede algo, se ha lanzado una excepción, la excepción es el método de cambio de base de datos.
Esperado: ''A'' y ''B'' no cambiaron nada. la actualización que ''A'' hizo, se revertirá. ''B'' no cambió nada, bueno ... hubo una excepción.
Actual: la actualización ''A'' no parece haberse revertido. ''B'' no cambió nada, bueno ... hubo una excepción.
Cierto código
Si tuviera la conexión, haría algo como:
private void databaseChanges(Connection conn) {
try {
conn.setAutoCommit(false);
A(); //update.
B(); //insert
conn.commit();
} catch (Exception e) {
try {
conn.rollback();
} catch (Exception ei) {
//logs...
}
} finally {
conn.setAutoCommit(true);
}
}
El problema: no tengo la conexión (ver las etiquetas que se publican con la pregunta)
Lo intenté:
@Service
public class SomeService implements ISomeService {
@Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
@Autowired
private NamedParameterJdbcTemplate npjt;
@Transactional
private void databaseChanges() throws Exception {
A(); //update.
B(); //insert
}
}
Mi clase AppConfig:
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public NamedParameterJdbcTemplate namedParameterJdbcTemplate() {
return new NamedParameterJdbcTemplate(dataSource);
}
}
''A'' hace la actualización. de ''B'' se ha lanzado una excepción. La actualización realizada por ''A'' no se ha retrotraído.
Por lo que leí, entiendo que no estoy usando el @Transactional correctamente. Leí y probé varias publicaciones en blogs y preguntas y respuestas de stackverflow sin poder resolver mi problema.
¿Alguna sugerencia?
EDITAR
Hay un método que llama al método databaseChanges ()
public void changes() throws Exception {
someLogicBefore();
databaseChanges();
someLogicAfter();
}
Qué método debe ser anotado con @Transactional,
cambios ()? databaseChanges ()?
Prueba esto:
@TransactionManagement(TransactionManagementType.BEAN)
public class MyFacade {
@TransactionAttribute(TransactionAttribute.REQUIRES_NEW)
public void databaseChanges() throws Exception {
A(); //update.
B(); //insert
}
Lo que parece que se está perdiendo es un TransactionManager
. El objetivo de TransactionManager
es poder gestionar las transacciones de la base de datos. Hay 2 tipos de transacciones, programáticas y declarativas. Lo que está describiendo es la necesidad de una transacción declarativa a través de anotaciones.
Entonces, lo que necesita para su proyecto es lo siguiente:
Dependencia de transacciones de primavera (usando Gradle como ejemplo)
compile("org.springframework:spring-tx")
Definir un administrador de transacciones en Spring Boot Configuration
Algo como esto
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource)
{
return new DataSourceTransactionManager(dataSource);
}
También necesitaría agregar la anotación @EnableTransactionManagement
(no estoy seguro de si esto es gratis en las versiones más recientes del arranque de primavera).
@EnableTransactionManagement
public class AppConfig {
...
}
Añadir @Transactional
Aquí debería agregar la anotación @Transactional
para el método que desea que participe en la transacción
@Transactional
public void book(String... persons) {
for (String person : persons) {
log.info("Booking " + person + " in a seat...");
jdbcTemplate.update("insert into BOOKINGS(FIRST_NAME) values (?)", person);
}
};
Tenga en cuenta que este método debe ser público y no privado. Es posible que desee considerar poner @Transactional
en el método público llamando a databaseChanges()
.
También hay temas avanzados sobre dónde debe ir @Transactional
y cómo se comporta, así que es mejor trabajar primero y luego explorar esta área un poco más tarde :)
Una vez que todos estos estén en su lugar (dependencia + configuración de transactionManager + anotación), las transacciones deberían funcionar en consecuencia.
Referencias
Spring Reference Documentation on Transactions
Guía de primavera para las transacciones que usan Spring Boot : tiene un código de muestra con el que puede jugar
@Transactional
anotación @Transactional
en la primavera funciona envolviendo su objeto en un proxy que a su vez ajusta los métodos anotados con @Transactional
en una transacción. Debido a que la anotación no funcionará en métodos privados (como en su ejemplo) porque los métodos privados no se pueden heredar => no se pueden envolver (esto no es cierto si usa transacciones declarativas con aspectj , entonces las advertencias relacionadas con proxy a continuación no se aplican).
Aquí hay una explicación básica de cómo @Transactional
magia de primavera @Transactional
.
Tu escribiste:
class A {
@Transactional
public void method() {
}
}
Pero esto es lo que realmente obtienes cuando te inyectas un frijol:
class ProxiedA extends A {
private final A a;
public ProxiedA(A a) {
this.a = a;
}
@Override
public void method() {
try {
// open transaction ...
a.method();
// commit transaction
} catch (RuntimeException e) {
// rollback transaction
} catch (Exception e) {
// commit transaction
}
}
}
Esto tiene limitaciones. No funcionan con los métodos de @PostConstruct
porque se llaman antes de que el objeto sea proxy. E incluso si configuró todos correctamente, las transacciones solo se retrotraen en excepciones sin marcar de forma predeterminada. Utilice @Transactional(rollbackFor={CustomCheckedException.class})
si necesita deshacer una excepción marcada.
Otra advertencia frecuente que conozco:
@Transactional
método @Transactional
solo funcionará si lo llama "desde afuera", en el siguiente ejemplo b()
no se incluirá en la transacción:
class X {
public void a() {
b();
}
@Transactional
public void b() {
}
}
También es porque @Transactional
funciona al @Transactional
tu objeto. En el ejemplo anterior, a()
llamará a Xb()
no a un método mejorado "proxy de primavera" b()
por lo que no habrá transacción. Como solución, debe llamar a b()
desde otro bean.
Cuando encuentre alguna de estas advertencias y no pueda utilizar una solución alternativa sugerida (haga que el método no sea privado o llame a b()
desde otro bean) puede usar TransactionTemplate
lugar de transacciones declarativas:
public class A {
@Autowired
TransactionTemplate transactionTemplate;
public void method() {
transactionTemplate.execute(status -> {
A();
B();
return null;
});
}
...
}
Actualizar
Respondiendo a OP pregunta actualizada utilizando la información anterior.
¿Qué método debe ser anotado con @Transactional: changes ()? databaseChanges ()?
@Transactional(rollbackFor={Exception.class})
public void changes() throws Exception {
someLogicBefore();
databaseChanges();
someLogicAfter();
}
Asegúrese de que los changes()
se llamen "desde afuera" de un bean, no de la clase en sí y después de la afterPropertiesSet()
instancias del contexto (por ejemplo, esto no es afterPropertiesSet()
o @PostConstruct
método anotado). Comprenda que la transacción de rollbacks de primavera solo se realiza por excepciones sin marcar de forma predeterminada (intente ser más específico en la reversión para la lista de excepciones verificadas).
Cualquier
RuntimeException
desencadena reversión, y cualquier Excepción marcada no lo hace.
Este es un comportamiento común en todas las API de transacciones de Spring. De forma predeterminada , si se lanza una RuntimeException
dentro del código transnacional, la transacción se retrotraerá. Si se lanza una excepción marcada (es decir, no una RuntimeException
), la transacción no se revertirá.
Depende de qué excepción obtenga dentro de la función de cambio de base de datos. Entonces, para capturar todas las excepciones, todo lo que necesita hacer es agregar rollbackFor = Exception.class
El cambio se supone que debe estar en la clase de servicio, el código será así:
@Service
public class SomeService implements ISomeService {
@Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
@Autowired
private NamedParameterJdbcTemplate npjt;
@Transactional(rollbackFor = Exception.class)
private void databaseChanges() throws Exception {
A(); //update
B(); //insert
}
}
Además, puede hacer algo agradable con él, así que no todo el tiempo tendrá que escribir rollbackFor = Exception.class
. Puede lograrlo escribiendo su propia anotación personalizada:
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(rollbackFor = Exception.class)
@Documented
public @interface CustomTransactional {
}
El código final será así:
@Service
public class SomeService implements ISomeService {
@Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
@Autowired
private NamedParameterJdbcTemplate npjt;
@CustomTransactional
private void databaseChanges() throws Exception {
A(); //update
B(); //insert
}
}
El primer código que presenta es para UserTransactions, es decir, la aplicación tiene que hacer la gestión de transacciones. Por lo general, usted quiere que el contenedor se encargue de eso y use la anotación @Transactional. Creo que el problema en tu caso podría ser que tienes la anotación en un método privado. Movería la anotación al nivel de clase
@Transactional
public class MyFacade {
public void databaseChanges() throws Exception {
A(); //update.
B(); //insert
}
Entonces debería deshacerse correctamente. Puede encontrar más detalles aquí. ¿Funciona el atributo Spring @Transactional en un método privado?
Lo que necesitas es algo como esto:
@Transactional(propagation=Propagation.REQUIRES_NEW, rollbackFor = {Exception.class})
public void databaseChanges() throws Exception {
A(); //update.
B(); //insert
}
@Transactional(propagation=Propagation.REQUIRED, rollbackFor = {Exception.class})
public void A() throws Exception {
// update
}
@Transactional(propagation=Propagation.REQUIRED, rollbackFor = {Exception.class})
public void B() throws Exception {
// insert
}
Al especificar la propagación como REQUIRES_NEW
se asegura de que se inicia una nueva transacción para el método databaseChanges()
y A()
y B()
participan en la misma transacción con su propagación especificada como REQUIRED
.
Debes asegurarte de que los métodos que estás anotando con @Transactional
anotaciones @Transactional
sean públicos porque el consejo transaccional se aplica solo a los métodos públicos . Un método privado anotado como tal NO arrojaría un error pero NO mostraría el comportamiento transaccional.
Ahora, cuando se producirá una excepción en B()
durante la inserción, el administrador de transacciones verifica internamente las reglas de retrotracción (que se especifican mediante rollbackFor); encuentra Exception.class y marca la transacción (iniciada en databaseChanges ()) como rollback-only y retrotraerá A () junto con ella ya que A () está participando en la misma transacción
Si esto no resuelve su problema, habilite los registros de rastreo de Springframework y bríndenme eso. Los límites de las transacciones y los sucesos se registran con precisión en estos registros cuando están habilitados.
Si no ve el registro de transacciones adecuado, entonces verifique si la gestión de transacciones está realmente habilitada en su aplicación. Agregar @EnableTransactionManagement
en la clase de configuración lo habilita.
Campo de golf:
Propagación
RollbackFor
Transaccional en métodos privados
@EnableTransactionManagement