This is a pretty confusing one. I've read dozens of links that purport to explain how to use #Transactional but I've verified no transaction is being created.
Main.java
#SpringBootApplication
#EnableJpaRepositories(basePackages="com.mypackage")
#EnableTransactionManagement
#EntityScan(basePackages=["com.mypackage"])
#EnableJpaAuditing
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
SubscriptionController.java
#RequestMapping("/api/subscription")
#RestController
public class SubscriptionController {
#Autowired SubscriptionService subscriptionService;
Logger log = LogManager.getLogger(this.getClass().getName());
public Collection<Subscriptions> subscribe(...) {
log.info("transName: " + TransactionSynchronizationManager.getCurrentTransactionName + ", isAlive: " + TransactionSynchronizationManager.isActualTransactionActive());
return subscriptionService.getAllSubscriptions();
}
}
SubscriptionService.java
#Service
public class SubscriptionService {
#Transactional public Collection<Subscription> getAllSubscriptions() {
log.info("transName: " + TransactionSynchronizationManager.getCurrentTransactionName() + ", isAlive: " + TransactionSynchronizationManager.isActualTransactionActive());
//return subscriptions via JPQL queries here
}
}
build.gradle
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.0.RELEASE")
}
}
plugins {
id 'war'
}
apply plugin: 'org.springframework.boot'
repositories {
mavenCentral()
}
def springVersion = '5.0.3.RELEASE'
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.3.11'
testCompile group: 'junit', name: 'junit', version: '4.12'
compile "org.springframework.security:spring-security-core:${springVersion}", exclude
compile "org.springframework.security:spring-security-config:${springVersion}", exclude
compile "org.springframework.security:spring-security-web:${springVersion}", exclude
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile "org.springframework:spring-test:${springVersion}", exclude
implementation 'org.springframework.boot:spring-boot-starter-web:2.0.5.RELEASE', exclude
compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '2.0.5.RELEASE'
compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.11'
compile 'org.liquibase:liquibase-core:3.6.1'
compile 'org.liquibase:liquibase-groovy-dsl:1.2.2'
}
So when I run the api call, I get null, false as the log output. The contract for #Transactional says that the transactional aspect code will be weaved into the annotated transactional method, such that there will be a transaction (and thus an entitymanager and db connection) set up before the method and closed some time afterward. But that is irrelevant becuase shouldn't spring be creating an entitymanager before the controller is run? Both things aren't working here. Neither spring, nor #Transactional, is setting up any transaction. This results in failure to do any kind of query except for what's doable via a subclasses of JpaRepository. Somehow my Jpa repositories are able to set up transactions for their own methods. But what if their results have lazily initialized properties? I need a hibernate session to get those. So I need a transaction.
Try to remove
#EnableJpaRepositories(basePackages="com.mypackage")
#EnableTransactionManagement
SpringBoot should do those things automatically
What class are you using for #Transactional?
You should use org.springframework.transaction.annotation.Transactional.
An out-of-the-box Spring-boot application using spring initializer (https://start.spring.io/) will handle transactions properly. If you make yourself a sandbox with a unit test you can observer that the transaction demarcation done in your example will produce a transaction. Here is my git hub example: https://github.com/skjenco/hibernateSandbox.git
Code:
#Test
#Transactional
public void test() {
logger.info(TransactionSynchronizationManager.getCurrentTransactionName());
.....
For transaction demarcation to work you must be in an object managed by a Spring (Component, Service, Managed Beans, etc). In the case above it appears that you are in a Spring managed service. Using a working sandbox may be helpful in trouble shooting your issue--that is what I have done to solve perplexing hibernate issues.
You may also want to post your pom.xml file. spring-data or spring-tx dependency should be added for auto configurer to create transaction manager. Otherwise explicitly create a transaction manager bean and run the code again.
You can enable TRACE level logs for org.springframework and see if transaction manager is initialized.
Also check transaction interceptor logs. Use logging.level.org.springframework.transaction.interceptor=TRACE
Related
Spring Boot 2.6.6 - Actuator API Reference
I checked the above link and unable to find the /actuator/pause endpoint. I was not sure if it was my app that was causing the issue, so I created a new MVP from spring initializer and even then pause endpoint is not there.
I remember that there used to be a POST endpoint /actuator/pause, but how do I enable this on newer versions of Spring Boot(2.6.6 above)?
The below is my MVP's code.
build.gradle
plugins {
id 'org.springframework.boot' version '2.6.6'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
application.yml
management:
endpoints.web.exposure.include: '*'
endpoint:
pause.enabled: true
restart.enabled: true
resume.enabled: true
shutdown.enabled: true
And once I start the app and hit
curl --location --request POST 'localhost:8080/actuator/pause', its sending 404.
You'll need to make sure that Spring Cloud Commons is included in your project dependencies, since it looks like that's the library that supplies the actuator/pause endpoint (https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#endpoints).
As the documentation additionally notes:
If you disable the /actuator/restart endpoint then the /actuator/pause and /actuator/resume endpoints will also be disabled since they are just a special case of /actuator/restart.
It looks like you should add the following dependency to your build.gradle file
implementation 'org.springframework.cloud:spring-cloud-starter:3.1.1
You can see that RestartEndpoint and
RestartEndpoint.PauseEndpoint classes actually are defined in this package.
Obviously you should change the application.yml like this:
management:
endpoints.web.exposure.include: '*'
endpoint:
pause:
enabled: true
restart:
enabled: true
It's easy to support it by yourself.
#RestController
#RequestMapping("/health")
public class HealthController implements HealthIndicator {
private Health health = Health.up().build();
#RequestMapping("/pause")
#ResponseBody
public String pause() {
health = Health.down().build();
return "success";
}
#RequestMapping("/start")
#ResponseBody
public String start() {
health = Health.up().build();
return "success";
}
#Override
public Health health() {
return health;
}
}
I have a Spring Boot REST service and some unit tests written for the data layer. I use the embedded MongoDB dependency to perform the basic CRUD tests for my Repository class:
public interface UserRepository extends MongoRepository<UserEntity, String> {
Optional<UserEntity> findByUsername(String username);
}
I load the data from a JSON file (located under test/java/resources/data) and with the help of an ObjectMapper instance, I load the data into the embedded DB before each test and drop the collection after it's completed:
#DataMongoTest
class UserRepositoryTest {
// the path to the JSON file
private final File USER_DATA_JSON = Paths.get("src", "test", "resources", "data", "UserData.json").toFile();
// used to load a JSON file into a list of Users
private final ObjectMapper objectMapper = new ObjectMapper();
#Autowired
private MongoTemplate mongoTemplate; // makes the interaction with the embedded MongoDB much easier
#Autowired
private UserRepository userRepository;
#BeforeEach
void setUp() throws IOException {
// deserialize the JSON file to an array of users
UserEntity[] users = objectMapper.readValue(USER_DATA_JSON, UserEntity[].class);
// load each user into embedded MongoDB
Arrays.stream(users).forEach(mongoTemplate::save);
}
#AfterEach
void tearDown() {
// drop the users collection
mongoTemplate.dropCollection("users");
}
#Test
void testFindAllSuccess() {
// WHEN
List<UserEntity> users = userRepository.findAll();
// THEN
assertEquals(2, users.size(), "findAll() should return 2 users!");
}
// other test methods
}
In my local environment, everything works just fine, no configuration is needed inside the application.properties file. But when I execute a build on Jenkins, the following error appears for all the Repository tests:
UserRepositoryTest > testFindAllSuccess() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:132
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException at ConstructorResolver.java:800
Caused by: org.springframework.beans.factory.BeanCreationException at ConstructorResolver.java:658
Caused by: org.springframework.beans.BeanInstantiationException at SimpleInstantiationStrategy.java:185
Caused by: java.net.UnknownHostException at InetAddress.java:1642
Caused by: java.net.UnknownHostException at Inet6AddressImpl.java:-2
The build.gradle file dependencies are declared as follows:
implementation 'org.openapitools:openapi-generator-gradle-plugin:5.0.0'
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation "io.springfox:springfox-boot-starter:3.0.0"
implementation('org.modelmapper:modelmapper:2.3.0')
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo'
testImplementation 'io.projectreactor:reactor-test'
compile 'io.springfox:springfox-swagger-ui:3.0.0'
annotationProcessor group: 'org.springframework.boot', name: 'spring-boot-configuration-processor'
compile 'org.mongodb:mongodb-driver-sync'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-test'
// https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt
implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'
I assume Spring Boot is not identifying the embedded MongoDB anymore. How can I configure it as simple as possible?
I saw there is a possibility of using containerized MongoDB and test it using #TestContainers, but for now I need to fix this issue since every build fails due to the tests.
LATER EDIT: By activating the --debug option in Jenkins when running the build, I discovered the following cause:
java.net.UnknownHostException: dev096.dev.cloud.******.eu: dev096.dev.cloud.******.eu: Name or service not known
Do you know if I have to add that address to the known hosts to my local machine (etc/hosts) or it should be configured locally with profiles (localhost for development and dev096.dev.cloud.******.eu for production)?
I managed to reproduce the same error locally and, if you haven't solved it yet, this is my take.
Remove all MongoDB custom configurations/properties, then add the following class to your project:
#Configuration
public class MongoTestConfig {
#Bean
MongoProperties properties() {
MongoProperties mongoProperties = new MongoProperties();
mongoProperties.setPort(33333);
mongoProperties.setHost("localhost");
return mongoProperties;
}
}
Make sure you tag your test class (UserRepositoryTest) with these three annotations:
#RunWith(SpringRunner.class)
#DataMongoTest
#Import(MongoTestConfig.class)
#DataMongoTest ignores all other bean definitions so we force the test to include the configuration that we just created using the explicit #Import.
Now the test will be forced to run on localhost. Hope it's going to work for you as it did for me! 😁
If dev096.dev.cloud.******.eu is a host used only in production, then your production server needs to know that host; not your local PC, nor Jenkins.
Ideally, you'd run your Jenkins tests using a 'jenkins' Spring profile, and then define the localhost in application-jenkins.properties.
Unfortunately, none of your solutions worked for me, but I found out later that dev096.dev.cloud.******.eu is the Jenkins instance itself.
The fix was to add an entry in /etc/hosts/ such that the server's IP to be also recognized as dev096.dev.cloud.******.eu
Currently, I am doing a small project in which I need to use Spring Boot and FreeMarker template engine. I have tried different ways but I still cannot return a FreeMarker view from Spring Boot. My project uses Gradle as the build tool, here's what's inside:
build.gradle:
plugins {
id 'org.springframework.boot' version '2.3.3.RELEASE'
id 'java'
}
apply plugin: 'io.spring.dependency-management'
sourceCompatibility = 11
repositories {
mavenCentral()
}
sourceSets.main.resources.srcDirs = ["src/resources"]
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
compile("org.springframework.boot:spring-boot-starter-web")
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'org.springframework.boot:spring-boot-starter-freemarker'
}
Here is my method which I expect to redirect to the FreeMarker view with a list, instead, when I send a GET request to this URL, I just get the "index" string displayed:
#GetMapping(path = "/code/latest")
public String getTop10LatestCode(#ModelAttribute("model") ModelMap model) {
model.addAttribute("codes", codes.subList(Math.max(0, codes.size() - 10), Math.max(codes.size() - 1, 0)));
return "index";
}
Here is my application.properties file:
server.port=8889
management.endpoints.web.exposure.include=*
management.endpoint.shutdown.enabled=true
spring.freemarker.template-loader-path=classpath:/templates/
spring.freemarker.suffix=.ftlh
My FreeMarker view is already under the templates folder inside the resources folder.
Here is my project structure:
Any help would be much appreciated, thanks.
I have figured it out, my class is annotated with the #RestController annotation, which itself is the combination of the #Controller and #ResponseBody annotations, because each method in my class will have a response body returned, even the one that I posted here, that's why it returns the body, hence returns index. I need to change my #RestController to #Controller and the problem solved.
I am fairly new to working with Spring and I've come across a problem I cannot seem to resolve. I am trying to work with a package called Optaplanner. In this case I'm more or less following along with a simple course scheduler the video is here and the part I am working on happens at 51:00.
My problem is, one step of the process requires a dependency injection and when I build/run the Spring application I get the following error:
Description:
Field solverManager in com.java.optaplex.resource.MainResource required a bean of type 'org.optaplanner.core.api.solver.SolverManager' that could not be found.
The injection point has the following annotations:
- #javax.inject.Inject()
Action:
Consider defining a bean of type 'org.optaplanner.core.api.solver.SolverManager' in your configuration.
Process finished with exit code 1
I have used Gradle to manage my optaplanner package and I can see that the dependency is present (it is an interface called SolverManager). I cannot figure out why it cannot find/generate the bean however. I am assuming Spring would generate the bean on the fly much like a JPA repository's bean is created when using the #Autowired decorator. I have tried copying and pasting the SolverManager code to its own file under my project root and tried a few decorators on it (eg. #Component) in hopes that Spring would detect it, yet it still throws the same error and I am unsure what to do.
I have created a very simple demo app on GitHub here that has the error. The error is in the file src/main/java/com/java/optaplex/resource/MainResource.java
Also, in IntelliJ the SolverManager instance solverManager (see code below) is highlighted and says:
Could not autowire. No beans of 'SolverManager<Test, Long>' type found.
MainResource.java:
package com.java.optaplex.resource;
import com.java.optaplex.pojo.Test;
import org.optaplanner.core.api.solver.SolverManager;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.inject.Inject;
#RestController
#RequestMapping("/api")
public class MainResource {
// THE OFFENDING LINE IS HERE
#Inject
SolverManager<Test, Long> solverManager;
#GetMapping("/test")
public Test test () {
return new Test("Result message here");
}
}
For clarity, my Test class in the declaration of the Solver manager is:
Test.java
package com.java.optaplex.pojo;
public class Test {
private String message;
public Test(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
I realize my example code doesn't actually make use of the SolverManager, I just want the error on build to stop so I can proceed developing this. I just wanted to provide a very simple case that caused the error.
Lastly, my Gradle dependencies look like this:
build.gradle:
plugins {
id 'org.springframework.boot' version '2.2.6.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
id 'war'
}
group = 'com.java'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
// https://mvnrepository.com/artifact/org.optaplanner/optaplanner-core
compile group: 'org.optaplanner', name: 'optaplanner-core', version: '7.36.1.Final'
compile group: 'javax.inject', name: 'javax.inject', version: '1'
}
test {
useJUnitPlatform()
}
To make it work you just need to replace the #Inject annotation with #Autowire.
From the latest versions of Spring, the best practice is to use constructor dependency injection instead of #Autowire (the annotation is not required anymore as Spring will autowire beans automatically.
Try this:
#RestController
#RequestMapping("/api")
public class MainResource {
// Just the definition here
private SolverManager<Test, Long> solverManager;
pubilc MainResource(SolverManager<Test, Long> solverManager) {
// When Spring will start, the bean will be injected here and become
// available in this class
this.solverManager = solverManager;
}
#GetMapping("/test")
public Test test () {
// .....
}
// Other controller methods ....
}
I'm using IntelliJ, and it reports the error to me too. But I'm using the same code that I've posted in this answer and it works.
As you stated, the SolverManager bean is configured inside the optaplanner artifact, I suspect that the problem here is only the wrong annotation being used.
The IDE error bothers me too! But for the moment it works and I will leave it like this. In case I'll find a solution then I will add it here.
I am developing a API REST of route optimization with Spring Boot using OptaPlanner. I am just getting into the work with OptaPlanner and at one point I had this same problem. I had 'optaplanner-spring-boot-starter' and I added spring-boot-configuration-processor', with the idea of having the bean creation configuration that allows injecting the SolverManager in my classes, without success. Also I tried to change to other versions of OptaPlanner and without success. Finally I solved the problem adding the configuration that OptaPlanner includes in its code with the class 'RouteOptimizerConfig'. This class creates the bean of the solver, in such a way that already later I could inject it. This problem seems to be related to the fact that at a certain point the OptaPlanner SolverManager is overwritten.
Try this and add this class to your project:
#Configuration
class SolverConfig {
private final SolverFactory<VehicleRoutingSolution> solverFactory;
SolverConfig (SolverFactory<VehicleRoutingSolution> solverFactory) {
this.solverFactory = solverFactory;
}
#Bean
Solver<VehicleRoutingSolution> solver() {
return solverFactory.buildSolver();
}
}
It turns out, after a lot of online searches and digging through the Optaplanner Spring Boot packages the SolverManager bean is not provided for Spring. It seems the development focuses on the Quarkus framework.
I figured it was a good chance to try out Quarkus and once I made the change my code worked. There are some good videos on Youtube by the lead developer Geoffrey De Smet on setting up Optaplanner via Quarkus.
The following code produces a cyclic dependency error.
#Controller
public class Controllers {
#Autowired
JdbcTemplate jdbcTemplate;
#RequestMapping(value = "/", method = {RequestMethod.GET, RequestMethod.POST})
#ResponseBody
public String map(){
String sql = "INSERT INTO persons " +
"(id, name, surname) VALUES (?, ?, ?)";
Connection conn = null;
jdbcTemplate.execute("INSERT INTO persons (id, name, surname) VALUES (1, \'Name\', \'Surname\')");
return "";
}
#Bean
#Primary
public DataSource dataSource() {
return DataSourceBuilder
.create()
.username("root")
.password("root")
.url("jdbc:mysql://localhost:3306/people")
.driverClassName("com.mysql.jdbc.Driver")
.build();
}
The dependencies of some of the beans in the application context form a cycle:
| org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
↑ ↓
| controllers (field org.springframework.jdbc.core.JdbcTemplate controllers.Controllers.jdbcTemplate)
↑ ↓
| org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration
↑ ↓
| dataSource
└─────┘
but if I do not autowire jdbctemplate and initialize it normally
jdbcTemplate = new JdbcTemplate(dataSource());
then no errors are produced
I have the following gradle dependencies:
dependencies {
compile("org.springframework.boot:spring-boot-starter-web:1.5.3.RELEASE")
compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '1.5.2.RELEASE'
compile(group: 'mysql', name: 'mysql-connector-java', version: '6.0.6')
testCompile group: 'junit', name: 'junit', version: '4.12'
}
What is the reason behind the cyclic dependency?
You have a cyclic dependency because the JdbcTemplate needs a DataSource but for the DataSource to be created an instance of the Controllers is needed, but because that needs a JdbcTemplate it cannot be constructed (due to the cyclic dependency).
You are using Spring Boot but apparently trying hard not to. Remove your #Bean method for the DataSource and add the following to the application.properties.
spring.datasource.url=jdbc:mysql://localhost:3306/people
spring.datasource.username=root
spring.datasource.password=root
With this spring boot will provide a preconfigured DataSource for you.
Pro-Tip
Something else you are mixing versions of Spring Boot 1.5.2 and 1.5.3 never mix versions of a framework as that is trouble waiting to happen. Just remove all versions, and assuming you are using Spring Boot with Gradle properly, you will have a single managed version.