I met this exception with springboot 1.5.3 + hibernate5.
And ,i used the #EnableTransactionManagement at the start application class. Have the transaction aop configuration as follow code...
#Aspect
#Configuration
public class TransactionManagerConfig {
private static final String AOP_POINTCUT_EXPRESSION="execution (* *..service..*Service*.*(..))";
#Autowired
#Qualifier("transactionManager")
private HibernateTransactionManager transactionManager;
......
......
It worked very nice until yestardy.And then,i create a service named c.a.b.service.MessageCoreService.
#Service
public class MessageCoreService {
#Autowired
private MessageSourceResolverMapper mapper;
#Autowired
private TenantIdentifierResolver tir;
public Map<String, MsgDetail> fetchCode(String code, Locale locale) {
System.err.println("Thread.currentThread().getName() at MessageCoreService:" + Thread.currentThread().getName());
System.err.println("request teanantId:" + tir.resolveCurrentTenantIdentifier());
System.err.println("locale3 at MessageCoreService:" + locale.toString());
MsgDetail enMsg = mapper.resolveCode(code, MessageSourceResolver.DEFAULT_LOCALE);
MsgDetail localeMsg = mapper.resolveCode(code, locale.toString());
Map<String, MsgDetail> ret = new HashMap<String, MsgDetail>();
ret.put("defaultLocale", enMsg);
ret.put("requestLocale", localeMsg);
return ret;
}
In the controller, do a test first.
#Autowired
private MessageCoreService msgService;
#RequestMapping("/test")
#ResponseBody
public Map<String, MsgDetail> test() {
System.err.println("111111111111111111111111111111111:");
TenantIdentifierResolver.tempOverideTenantId(ZAppUtils.getInitDatabaseId()); // change to init database
ServiceResult<Module> rslt = new ServiceResult<Module>();
Locale locale = new Locale("en", "US");
System.err.println("Thread.currentThread().getName() at ResourcesModuleController:" + Thread.currentThread().getName());
System.err.println("request teanantId:" + tir.resolveCurrentTenantIdentifier());
return msgService.fetchCode("e90001.5", locale);
Then, the hibernate exception "Could not obtain transaction-synchronized Session for current thread" occured.
I checked over and over,everything is ok.But it's aways occured.
I don't understand any of this. And then,i coped c.a.b.service.MessageCoreService. to c.a.c.service.MessageAdminService. Yes,just change a direcory,rename it.
In the controller,add a method.
#Autowired
private MessageCoreAdminService msgAdminService;
#RequestMapping("/test2")
#ResponseBody
public Map<String, MsgDetail> test2() {
System.err.println("111111111111111111111111111111111:");
TenantIdentifierResolver.tempOverideTenantId(ZAppUtils.getInitDatabaseId()); // change to init database
ServiceResult<Module> rslt = new ServiceResult<Module>();
Locale locale = new Locale("en", "US");
System.err.println("Thread.currentThread().getName() at ResourcesModuleController:" + Thread.currentThread().getName());
return msgAdminService.fetchCode("e90001.5", locale);
}
And then ,an incredible scene appeared. When i access the test2,it worked normally, but the test method ,it still show me the "transaction-synchronized Session" exception. Oh ,I feel like I'm breaking down. I can't resolve the problem.
Pls help.
Related
I am creating a Kafka Spring producer under Spring Boot which will send data to Kafka and then write to a database; I want all that work to be in one transaction. I am new to Kafka and no expert on Spring, and am having some difficulty. Any pointers much appreciated.
So far my code writes to Kafka successfully in a loop. I have not yet set up
the DB, but have proceeded to set up global transactioning by adding a transactionIdPrefix to the producerFactory in the configuration:
producerFactory.setTransactionIdPrefix("MY_SERVER");
and added #Transactional to the method that does the Kafka send. Eventually I plan to do my DB work in that same method.
Problem: the code runs great the first time. But if I stop the program, even cleanly, I find that the code hangs the 2nd time I run it as soon as it enters the #Transactional method. If I comment out the #Transactional, it enters the method but hangs on the kafa template send().
The problem seems to be the transaction ID. If I change the prefix and rerun, the program runs fine again the first time but hangs when I run it again, until a new prefix is chosen. Since after a restart the trans ID counter starts at zero, if the trans ID prefix does not change then the same trans ID will be used upon restart.
It seems to me that the original transID is still open on the server, and was never committed. (I can read the data off the topic using the console-consumer, but that will read uncommitted). But if that is the case, how do I get spring to commit the trans? I am thinking my coniguration must be wrong. Or-- is the issue possibly that trans ID's can never be reused? (In which case, how does one solve that?)
Here is my relevant code. Config is:
#SpringBootApplication
public class MYApplication {
#Autowired
private static ChangeSweeper changeSweeper;
#Value("${kafka.bootstrap-servers}")
private String bootstrapServers;
#Bean
public ProducerFactory<String, String> producerFactory() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
DefaultKafkaProducerFactory<String, String> producerFactory=new DefaultKafkaProducerFactory<>(configProps);
producerFactory.setTransactionIdPrefix("MY_SERVER");
return producerFactory;
}
#Bean
public KafkaTransactionManager<String, String> KafkaTransactionManager() {
return new KafkaTransactionManager<String, String>((producerFactory()));
}
#Bean(name="kafkaProducerTemplate")
public KafkaTemplate<String, String> kafkaProducerTemplate() {
return new KafkaTemplate<>(producerFactory());
}
And the method that does the transaction is:
#Transactional
public void send( final List<Record> records) {
logger.debug("sending {} records; batchSize={}; topic={}", records.size(),batchSize, kafkaTopic);
// Divide the record set into batches of size batchSize and send each batch with a kafka transaction:
for (int batchStartIndex = 0; batchStartIndex < records.size(); batchStartIndex += batchSize ) {
int batchEndIndex=Math.min(records.size()-1, batchStartIndex+batchSize-1);
List<Record> nextBatch = records.subList(batchStartIndex, batchEndIndex);
logger.debug("## batch is from " + batchStartIndex + " to " + batchEndIndex);
for (Record record : nextBatch) {
kafkaProducerTemplate.send( kafkaTopic, record.getKey().toString(), record.getData().toString());
logger.debug("Sending> " + record);
}
// I will put the DB writes here
}
This works fine for me no matter how many times I run it (but I have to run 3 broker instances on my local machine because transactions require that by default)...
#SpringBootApplication
#EnableTransactionManagement
public class So47817034Application {
public static void main(String[] args) {
SpringApplication.run(So47817034Application.class, args).close();
}
private final CountDownLatch latch = new CountDownLatch(2);
#Bean
public ApplicationRunner runner(Foo foo) {
return args -> {
foo.send("foo");
foo.send("bar");
this.latch.await(10, TimeUnit.SECONDS);
};
}
#Bean
public KafkaTransactionManager<Object, Object> KafkaTransactionManager(KafkaProperties properties) {
return new KafkaTransactionManager<Object, Object>(kafkaProducerFactory(properties));
}
#Bean
public ProducerFactory<Object, Object> kafkaProducerFactory(KafkaProperties properties) {
DefaultKafkaProducerFactory<Object, Object> factory =
new DefaultKafkaProducerFactory<Object, Object>(properties.buildProducerProperties());
factory.setTransactionIdPrefix("foo-");
return factory;
}
#KafkaListener(id = "foo", topics = "so47817034")
public void listen(String in) {
System.out.println(in);
this.latch.countDown();
}
#Component
public static class Foo {
#Autowired
private KafkaTemplate<Object, Object> template;
#Transactional
public void send(String go) {
this.template.send("so47817034", go);
}
}
}
I have the following spring boot + data Rest repository:
#RepositoryRestResource(collectionResourceRel = "dto", path = "produtos")
public interface ProdutoRepository extends CrudRepository<Produto, Integer> {
#Query("SELECT p FROM Produto p where descricao LIKE CONCAT(UPPER(:like),'%')")
List<Produto> findByLike(#Param("like") String like);
}
I also have a java client that access this method (this is my example of doing it):
String url = "http://localhost:8080/produtos/search/findByLike?like={like}";
RestTemplate t = new RestTemplate();
ProdutoDto resp = t.getForObject(url, ProdutoDto.class, txtLoc.getText());
ProdutoDto (this one is not totally necessary):
public class ProdutoDto extends HalDto<Produto> {}
HalDto:
public class HalDto<T extends ResourceSupport> extends ResourceSupport {
#JsonProperty("_embedded")
private EmbeddedDto<T> embedded;
public EmbeddedDto<T> getEmbedded() {
return embedded;
}
public void setEmbedded(EmbeddedDto<T> embedded) {
this.embedded = embedded;
}
}
EmbeddedDto:
public class EmbeddedDto<T> {
#JsonProperty("dto")
private List<T> dtoList;
public List<T> getDtoList()
{
return dtoList;
}
public void setDto(List<T> dtoList) {
this.dtoList = dtoList;
}
}
Those classes are necessary (i think) because Spring Data returns data in the HAL (https://en.wikipedia.org/wiki/Hypertext_Application_Language) format.
Note: Produto must extend ResourceSupport.
Caveats: All collectionResourceRel must be named "dto" and it only works for collections (may be adjusted).
Is this the proper way to do this?
I have googled around and found plenty of examples of doing the server side, but almost nothing on building clients.
Thanks.
This is a solution that I have found which seems to work well.
First, setup your RestTemplate so that it expects JSON/HAL and knows what to do with it:
#Bean
public RestTemplate restTemplate() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jackson2HalModule());
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MappingJackson2HttpMessageConverter messageConverter =
new MappingJackson2HttpMessageConverter();
messageConverter.setObjectMapper(objectMapper);
messageConverter.setSupportedMediaTypes(Arrays.asList(MediaTypes.HAL_JSON, MediaType.APPLICATION_JSON_UTF8));
return new RestTemplate(Arrays.asList(messageConverter));
}
Then you can use the exchange method of the RestTemplate to specify that you want your result to be ResponseEntity<PagedResources<Producto>>
ResponseEntity<PagedResources<Producto>> resultResponse = restTemplate.exchange(uri, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference<PagedResources<Producto>>(){});
if(resultResponse.getStatusCode() == HttpStatus.OK){
Collection<Producto> results = resultResponse.getBody().getContent();
log.info("{} results obtained", results.size());
}
You can instantiate restTemplate by either calling the restTemplate() method defined above or you can inject (autowire) it.
I've already read this Q&A but it didn't solve the problem. I'm using Spring Boot 1.4.2.RELEASE and I'm attempting to speed up my tests. Up to this point, I've used #SpringBootTest and I'm testing switching some of these simpler tests to #WebMvcTest.
My controller has the following method which is responding to GET requests.
public ResponseEntity<MappingJacksonValue> fetchOne(#PathVariable Long id, #RequestParam(value = "view", defaultValue = "summary", required = false) String view) throws NotFoundException {
Brand brand = this.brandService.findById(id);
if (brand == null) {
throw new NotFoundException("Brand Not Found");
}
MappingJacksonValue mappingJacksonValue = jsonView(view, brand);
return new ResponseEntity<>(mappingJacksonValue, HttpStatus.OK);
}
My test looks like this:
#RunWith(SpringRunner.class)
#WebMvcTest(BrandController.class)
public class BrandSimpleControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private BrandService brandService;
#Test
public void testExample() throws Exception {
Brand brand = new Brand(1l);
brand.setName("Test Name");
brand.setDateCreated(new Date());
brand.setLastUpdated(new Date());
when(this.brandService.findById(1l)).thenReturn(brand);
this.mockMvc.perform(get("/api/brands/1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name", is("Test Name")));
}
}
When I run this test, I get nothing back in the content. I'm not doing anything significantly different than this guide, so not sure what I'm missing.
I should note that using #SpringBootTest with the exact same controller works as expected.
I am working with Spring-websocket and I have the following problem:
I am trying to put a placeholder inside a #MessageMapping annotation in order to get the url from properties. It works with #RequestMapping but not with #MessageMapping.
If I use this placeholder, the URL is null. Any idea or suggestion?
Example:
#RequestMapping(value= "${myProperty}")
#MessageMapping("${myProperty}")
Rossen Stoyanchev added placeholder support for #MessageMapping and #SubscribeMapping methods.
See Jira issue: https://jira.spring.io/browse/SPR-13271
Spring allows you to use property placeholders in #RequestMapping, but not in #MessageMapping. This is 'cause the MessageHandler. So, we need to override the default MessageHandler to do this.
WebSocketAnnotationMethodMessageHandler does not support placeholders and you need add this support yourself.
For simplicity I just created another WebSocketAnnotationMethodMessageHandler class in my project at the same package of the original, org.springframework.web.socket.messaging, and override getMappingForMethod method from SimpAnnotationMethodMessageHandler with same content, changing only how SimpMessageMappingInfo is contructed using this with this methods (private in WebSocketAnnotationMethodMessageHandler):
private SimpMessageMappingInfo createMessageMappingCondition(final MessageMapping annotation) {
return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.MESSAGE, new DestinationPatternsMessageCondition(
this.resolveAnnotationValues(annotation.value()), this.getPathMatcher()));
}
private SimpMessageMappingInfo createSubscribeCondition(final SubscribeMapping annotation) {
final SimpMessageTypeMessageCondition messageTypeMessageCondition = SimpMessageTypeMessageCondition.SUBSCRIBE;
return new SimpMessageMappingInfo(messageTypeMessageCondition, new DestinationPatternsMessageCondition(
this.resolveAnnotationValues(annotation.value()), this.getPathMatcher()));
}
These methods now will resolve value considering properties (calling resolveAnnotationValues method), so we need use something like this:
private String[] resolveAnnotationValues(final String[] destinationNames) {
final int length = destinationNames.length;
final String[] result = new String[length];
for (int i = 0; i < length; i++) {
result[i] = this.resolveAnnotationValue(destinationNames[i]);
}
return result;
}
private String resolveAnnotationValue(final String name) {
if (!(this.getApplicationContext() instanceof ConfigurableApplicationContext)) {
return name;
}
final ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) this.getApplicationContext();
final ConfigurableBeanFactory configurableBeanFactory = applicationContext.getBeanFactory();
final String placeholdersResolved = configurableBeanFactory.resolveEmbeddedValue(name);
final BeanExpressionResolver exprResolver = configurableBeanFactory.getBeanExpressionResolver();
if (exprResolver == null) {
return name;
}
final Object result = exprResolver.evaluate(placeholdersResolved, new BeanExpressionContext(configurableBeanFactory, null));
return result != null ? result.toString() : name;
}
You still need to define a PropertySourcesPlaceholderConfigurer bean in your configuration.
If you are using XML based configuration, include something like this:
<context:property-placeholder location="classpath:/META-INF/spring/url-mapping-config.properties" />
If you are using Java based configuration, you can try in this way:
#Configuration
#PropertySources(value = #PropertySource("classpath:/META-INF/spring/url-mapping-config.properties"))
public class URLMappingConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Obs.: in this case, url-mapping-config.properties file are in a gradle/maven project in src\main\resources\META-INF\spring folder and content look like this:
myPropertyWS=urlvaluews
This is my sample controller:
#Controller
public class WebSocketController {
#SendTo("/topic/test")
#MessageMapping("${myPropertyWS}")
public String test() throws Exception {
Thread.sleep(4000); // simulated delay
return "OK";
}
}
With default MessageHandler startup log will print something like this:
INFO: Mapped "{[/${myPropertyWS}],messageType=[MESSAGE]}" onto public java.lang.String com.brunocesar.controller.WebSocketController.test() throws java.lang.Exception
And with our MessageHandler now print this:
INFO: Mapped "{[/urlvaluews],messageType=[MESSAGE]}" onto public java.lang.String com.brunocesar.controller.WebSocketController.test() throws java.lang.Exception
See in this gist the full WebSocketAnnotationMethodMessageHandler implementation.
EDIT: this solution resolves the problem for versions before 4.2 GA. For more information, see this jira.
Update :
Now I understood what you mean, but I think that is not possible(yet).
Documentation does not mention anything related to Path mapping URIs.
Old answer
Use
#MessageMapping("/handler/{myProperty}")
instead of
#MessageMapping("/handler/${myProperty}")
And use it like this:
#MessageMapping("/myHandler/{username}")
public void handleTextMessage(#DestinationVariable String username,Message message) {
//do something
}
#MessageMapping("/chat/{roomId}")
public Message handleMessages(#DestinationVariable("roomId") String roomId, #Payload Message message, Traveler traveler) throws Exception {
System.out.println("Message received for room: " + roomId);
System.out.println("User: " + traveler.toString());
// store message in database
message.setAuthor(traveler);
message.setChatRoomId(Integer.parseInt(roomId));
int id = MessageRepository.getInstance().save(message);
message.setId(id);
return message;
}
I'm trying to write unit test for this controller method
#PreAuthorize("hasRole(#d.code) or hasRole('ROLE_ALL_ACCESS')")
#RequestMapping(value = "{department}/{examination}", method = RequestMethod.GET)
public String show(ModelMap model, Principal principal, Locale locale, D d) {
model.addAttribute(service.getItem(d) //THIS CAUSES NULL ON TEST
.addAttribute(new DateTime())
.addAttribute(locale);
return "show";
I tried this
#Test (expected=AccessDeniedException.class)
public void testShowAccessDenied() {
super.simulateRole("ROLE_STUDENT");
controller.show(new ModelMap(), Helper.getTestPrincipal(), Locale.getDefault(), new D());
But it causes NullPointerException on the marked line, which means test is running the method instead of throwing AccessDeniedException based on the wrong role.
My super test class is
public class AuthorizeTestBase {
private Mockery jmock = new JUnit4Mockery();
private AuthenticationManager authenticationManager= jmock.mock(AuthenticationManager.class);
#Before
public void setUp() {
jmock.checking(new Expectations() {{ ignoring(anything()); }});
}
protected void simulateRole(String role) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority(role));
PreAuthenticatedAuthenticationToken pat = new PreAuthenticatedAuthenticationToken(Helper.getTestPrincipal(), "", authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationManager.authenticate(pat));
}
Helper.getTestPrincipal is
LdapPerson.Essence essence = new LdapPerson.Essence();
LdapPerson user = (LdapPerson) essence.createUserDetails();
return new PreAuthenticatedAuthenticationToken(user, "password");
I think I'm missing something on mocking authenticationManager. Help me!
The NPE is happeing when you try to call the service. Your class AuthorizeTestBase doesn't know about any spring beans service/dao etc . So it should load the spring wire configuration. The below is an example.
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(locations = {"classpath:/applicationContext-test.xml",
"classpath:/applicationContext-ABC-test.xml",
"classpath:/applicationContext-DEF-test.xml",
"classpath:/applicationContext-serviceGHI-test.xml",
"classpath:/applicationContext-securityHIJ-test.xml"
})
public class AuthorizeTestBase {
.....
}
Also please have a look into - Spring MVC Test Framework