I tried to create tables in cassandra db on start-up of spring boot application but it doesn't seem to be able to create tables. Below is my configuration. I have the #EnableCassandraRepositories in my Application class. I already created my keyspace by default. So its just the tables that I'm looking to create.
Configuration
#Configuration
public class CassandraConfig extends AbstractCassandraConfiguration {
#Value("${cassandra.contactpoints}")
private String contactPoints;
#Value("${cassandra.port}")
private int port;
#Value("${cassandra.keyspace}")
private String keySpace;
#Value("${cassandra.basePackages}")
private String basePackages;
#Autowired
private Environment environment;
#Override
protected String getKeyspaceName() {
return keySpace;
}
#Override
#Bean
public CassandraClusterFactoryBean cluster() {
final CassandraClusterFactoryBean cluster = new CassandraClusterFactoryBean();
cluster.setContactPoints(contactPoints);
cluster.setPort(port);
return cluster;
}
#Override
#Bean
public CassandraMappingContext cassandraMapping() throws ClassNotFoundException {
return new BasicCassandraMappingContext();
}
}
Entity
#Table
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class AssessmentAttemptDetailsEntity implements Serializable {
#PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED)
private String assessmentId;
#PrimaryKeyColumn(type = PrimaryKeyType.CLUSTERED)
private String attempid;
}
Application
#SpringBootApplication
#ComponentScan(basePackages = {"com.lte.assessmentanalytics.service","com.lte.assessmentanalytics.config", "com.lte.assessmentanalytics.model", "com.lte.assessmentanalytics.listener"})
#EnableCassandraRepositories("com.lte.assessmentanalytics.model")
public class AssessmentanalyticsApplication {
#Autowired
private AssessmentAttemptRepository assessmentAttemptRepository;
public static void main(String[] args) {
SpringApplication.run(AssessmentanalyticsApplication.class, args);
}
}
Repository
#Repository
public interface AssessmentAttemptRepository extends CassandraRepository<AssessmentAttemptDetailsEntity, Long> {
}
I was able to fix this by modifying my CassandraConfig class to.
#Configuration
#EnableCassandraRepositories("com.lte.assessmentanalytics.model")
public class CassandraConfig extends AbstractCassandraConfiguration {
#Value("${cassandra.contactpoints}")
private String contactPoints;
#Value("${cassandra.port}")
private int port;
#Value("${cassandra.keyspace}")
private String keySpace;
#Value("${cassandra.basePackages}")
private String basePackages;
#Override
protected String getKeyspaceName() {
return keySpace;
}
#Override
protected String getContactPoints() {
return contactPoints;
}
#Override
protected int getPort() {
return port;
}
#Override
public SchemaAction getSchemaAction() {
return SchemaAction.CREATE_IF_NOT_EXISTS;
}
#Override
public String[] getEntityBasePackages() {
return new String[] {basePackages};
}
}
Related
I am getting "Couldn't find PersistentEntity for type class" error when I am using #EnableMongoAuditing features along with MongoRepository.
This happens when I save a document when collection isn't already present in database.
I tried whatever is mentioned in:
https://github.com/spring-projects/spring-boot/issues/12023
https://jira.spring.io/browse/DATAMONGO-1999
Spring boot mongodb auditing error
but nothing is working.
Mentioned things are:
Extend MongoConfig by AbstractMongoConfiguration and override all methods.
Here is my code which reproduced the same error:
MongoConfig class
#Configuration
public class MongoConfig extends AbstractMongoConfiguration {
#Value("${spring.data.mongodb.host}")
private String mongoHost;
#Value("${spring.data.mongodb.port}")
private String mongoPort;
#Value("${spring.data.mongodb.database}")
private String mongoDB;
#Override
public MongoDbFactory mongoDbFactory() {
return new SimpleMongoDbFactory(new MongoClient(mongoHost + ":" + mongoPort), mongoDB);
}
#Override
public MongoClient mongoClient() {
return new MongoClient(mongoHost, Integer.parseInt(mongoPort));
}
#Override
public MongoTemplate mongoTemplate() {
return new MongoTemplate(mongoDbFactory());
}
#Override
public MappingMongoConverter mappingMongoConverter() {
return new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory()), new MongoMappingContext());
}
#Override
protected String getDatabaseName() {
return mongoDB;
}
}
Person Collection class
#Document
public class Person {
#Id
private String id;
private String name;
#CreatedDate
private LocalDateTime createdAt;
#LastModifiedDate
private LocalDateTime lastModified;
// Getter Setters Constructors omitted for brevity
}
Main Application class
#EnableMongoAuditing
#EnableMongoRepositories ({"com.example.*", "org.apache.*"})
#SpringBootApplication
#ComponentScan({"com.example.*", "org.apache.*"})
public class DemoApplication implements CommandLineRunner {
#Autowired
PersonRepository personRepository;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
Person p1 = new Person("1", "prakhar");
personRepository.save(p1);
}
}
Expected Result is Person entity should be saved in database.
Actual Result is "Couldn't find PersistentEntity for type class Person" error
Looks like you ran into https://github.com/spring-projects/spring-boot/issues/12023
Extending AbstractMongoConfiguration will switch off Spring Boot's auto-configuration of various Mongo components and also customises the base packages that are used to scan for mappings. I would recommend that you don't use it in Spring Boot.
Update
I managed to get the example running with the configuration as simple as
#Configuration
public class MongoConfig {
#Value("${spring.data.mongodb.host}")
private String mongoHost;
#Value("${spring.data.mongodb.port}")
private String mongoPort;
#Value("${spring.data.mongodb.database}")
private String mongoDB;
#Bean
public MongoDbFactory mongoDbFactory() {
return new SimpleMongoDbFactory(new MongoClient(mongoHost + ":" + mongoPort), mongoDB);
}
#Bean
public MongoClient mongoClient() {
return new MongoClient(mongoHost, Integer.parseInt(mongoPort));
}
}
and the app class
#EnableMongoAuditing
#SpringBootApplication
public class DemoApplication implements CommandLineRunner {
#Autowired
PersonRepository personRepository;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
Thread.sleep(2000);
Person p1 = new Person("1", "prakhar");
personRepository.save(p1);
}
}
Notice that I followed my own advice and did't inherit from AbstractMongoConfiguration
Explanation
The problem lies in the initialization of
#Bean
public MappingMongoConverter mappingMongoConverter() {
return new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory()), new MongoMappingContext());
}
You simply call MongoMappingContext constructor, without calling setInitialEntitySet. Compare that with MongoDataConfiguration auto-configuration class.
#Bean
#ConditionalOnMissingBean
public MongoMappingContext mongoMappingContext(MongoCustomConversions conversions)
throws ClassNotFoundException {
MongoMappingContext context = new MongoMappingContext();
context.setInitialEntitySet(new EntityScanner(this.applicationContext)
.scan(Document.class, Persistent.class));
Class<?> strategyClass = this.properties.getFieldNamingStrategy();
if (strategyClass != null) {
context.setFieldNamingStrategy(
(FieldNamingStrategy) BeanUtils.instantiateClass(strategyClass));
}
context.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
return context;
}
Even worse, you don't register MongoMappingContext as a managed bean.
Due to this fact, auto-configuration class is still created. This leads to a race condition, I tried to run the original code and could easily reproduce the error, but with a breakpoint in AbstractMappingContext.addPersistentEntity the test always passed.
For me I resolved this issue by adding following method in MongoConfig if your class extends from AbstractMongoConfiguration
#Override
protected String getMappingBasePackage() {
return "com.companyName.modulename"
}
If MongoConfig extends from MongoConfigurationSupport then add below method
#Override
protected Collection<String> getMappingBasePackages() {
return Arrays.asList("com.companyName.module1","com.companyName.module2");
}
Note that in later case I can specify multiple package names as base packages.
I am new to Junits and Mockito, I am writing a Unit test class to test my service class CourseService.java which is calling findAll() method of CourseRepository.class which implements CrudRepository<Topics,Long>
Service Class
#Service
public class CourseService {
#Autowired
CourseRepository courseRepository;
public void setCourseRepository(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
public Boolean getAllTopics() {
ArrayList<Topics> topicList=(ArrayList<Topics>) courseRepository.findAll();
if(topicList.isEmpty())
{
return false;
}
return true;
}
}
Repository class
public interface CourseRepository extends CrudRepository<Topics,Long>{
}
Domain class
#Entity
#Table(name="Book")
public class Topics {
#Id
#Column(name="Topicid")
private long topicId;
#Column(name="Topictitle",nullable=false)
private String topicTitle;
#Column(name="Topicauthor",nullable=false)
private String topicAuthor;
public long getTopicId() {
return topicId;
}
public void setTopicId(long topicId) {
this.topicId = topicId;
}
public String getTopicTitle() {
return topicTitle;
}
public void setTopicTitle(String topicTitle) {
this.topicTitle = topicTitle;
}
public String getTopicAuthor() {
return topicAuthor;
}
public void setTopicAuthor(String topicAuthor) {
this.topicAuthor = topicAuthor;
}
public Topics(long topicId, String topicTitle, String topicAuthor) {
super();
this.topicId = topicId;
this.topicTitle = topicTitle;
this.topicAuthor = topicAuthor;
}
}
Following is the Junit class I have written but courseRepository is getting initialized to NULL and hence I am getting NullPointerException.
public class CourseServiceTest {
#Mock
private CourseRepository courseRepository;
#InjectMocks
private CourseService courseService;
Topics topics;
#Mock
private Iterable<Topics> topicsList;
#Before
public void setUp() {
MockitoAnnotations.initMocks(CourseServiceTest.class);
}
#Test
public void test_Get_Topic_Details() {
List<Topics> topics = new ArrayList<Topics>();
Mockito.when(courseRepository.findAll()).thenReturn(topics);
boolean result=courseService.getAllTopics();
assertTrue(result);
}
}
Change the setUp() method to:
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
Probably you are dealing with some problem on the framework to make the mocked class be injected by the framework.
I recommend to use Constructor Injection, so you don't need to rely on the reflection and #Inject/#Mock annotations to make this work:
#Service
public class CourseService {
private final CourseRepository courseRepository;
// #Autowired annotation is optional when using constructor injection
CourseService (CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
// .... code
}
The test:
#Test
public void test_Get_Topic_Details() {
List<Topics> topics = new ArrayList<Topics>();
Mockito.when(courseRepository.findAll()).thenReturn(topics);
CourseService courseService = new CourseService(courseRepository);
boolean result = courseService.getAllTopics();
assertTrue(result);
}
I've got an entity
#Entity
#Table(name = "books")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "id", unique = true, nullable = false)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
I initialize it like this
#PostConstruct
public void init() {
List<String> newFiles = this.listFiles();
newFiles.forEach(filename -> {
Book book = new Book();
book.setName(filename);
dbRepository.save(book);
});
}
If I set the result of save to an instance of Book, I can get the id and it is not null—so id is created fine.
I defined a repository
#RepositoryRestResource
public interface IBooksRepository extends CrudRepository<Book, Long> {
}
which I'd like to use to get and set data into the books table in the database.
When I try to access my repository rest using curl localhost:8080/books, I get this response
{
"_embedded":{
"books":[
{
"name":"simple-file.txt",
"_links":{
"self":{
"href":"http://localhost:8080/books/1"
},
"book":{
"href":"http://localhost:8080/books/1"
}
}
}
]
},
"_links":{
"self":{
"href":"http://localhost:8080/books"
},
"profile":{
"href":"http://localhost:8080/profile/books"
}
}
}
The books element returns name only. How can I make it return id too, on the same level as name?
Spring Data Rest hides the ID by default, in order to have it in the JSON you have to manually configure that for your entity. Depending on your spring version you can either provide your own configuration (old):
#Configuration
public class ExposeEntityIdRestConfiguration extends RepositoryRestMvcConfiguration {
#Override
protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Book.class);
}
}
...or register a RepositoryRestConfigurer (current):
#Component
public class ExposeEntityIdRestMvcConfiguration extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Book.class);
}
}
See the Spring Data Rest documentation for more details.
The accepted answer overrides a deprecated method. Here's the updated version:
#Component
public class RestConfig implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
config.exposeIdsFor(Book.class);
}
}
An alternative approach is to implement RepositoryRestConfigurer in your #SpringBootApplication annotated class:
#SpringBootApplication
public class MyApplication implements RepositoryRestConfigurer {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
config.exposeIdsFor(Book.class);
}
}
There is now a static method RepositoryRestConfigurer.withConfig that does the same thing as above. See javadoc:
Convenience method to easily create simple {#link RepositoryRestConfigurer} instances that solely want to tweak the {#link RepositoryRestConfiguration}.
I found the usage in one of their integration tests
So the following approach would be more up to date as of now:
#Bean
public RepositoryRestConfigurer repositoryRestConfigurer()
{
return RepositoryRestConfigurer.withConfig(config -> {
config.exposeIdsFor(Book.class);
});
}
#Component
public class RestConfig implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Book.class);
//config.exposeIdsFor(Library.class);
}
}
This is a solution which works for all entities
#Autowired
private EntityManager entityManager;
#Bean
public RepositoryRestConfigurer repositoryRestConfigurer() {
return RepositoryRestConfigurer.withConfig(config -> config.exposeIdsFor(entityManager.getMetamodel().getEntities().stream().map(Type::getJavaType).toArray(Class[]::new)));
}
This is a good way to go.
#Projection(name = "customBook", types = { Book.class })
public interface CustomBook {
#Value("#{target.id}")
long getId();
}
credit: https://www.baeldung.com/spring-data-rest-projections-excerpts
I have Book & Bookdetail class
When I am trying to use same BookDetail information for my two Book objects using #ManyToOne, I am getting
"detached entity passed to persist error".
I tried same with JpaRepository and CrudRepository. But same result
Please help me. Thanks in advance
Main Class
#SpringBootApplication
public class HelloJpaApplication implements CommandLineRunner{
private static final Logger logger = LoggerFactory.getLogger(HelloJpaApplication.class);
#Autowired
private BookRepository bookRepository;
public void run(String... arg0) throws Exception {
Book book1=new Book();
book1.setName("my thoughts");
Book book2=new Book();
book2.setName("your thoughts");
Bookdetail detail=new Bookdetail();
detail.setCategory("good books");
book1.setBookdetail(detail);
book2.setBookdetail(detail);
bookRepository.save(book1);
bookRepository.save(book2);
}
public static void main(String[] args) {
SpringApplication.run(HelloJpaApplication.class, args);
}
}
BookRepository interface
public interface BookRepository extends JpaRepository<Book,Integer>{
}
Book class
#Entity
public class Book {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int bookid;
private String name;
#ManyToOne(cascade = {CascadeType.PERSIST,CascadeType.MERGE})
private Bookdetail bookdetail;
public Book() {
}
public Book(String name) {
this.name=name;
}
public int getBookid() {
return bookid;
}
public void setBookid(int bookid) {
this.bookid = bookid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Bookdetail getBookdetail() {
return bookdetail;
}
public void setBookdetail(Bookdetail bookdetail) {
this.bookdetail = bookdetail;
}
}
Bookdetail Class
#Entity
#Table(name="bookdetail")
public class Bookdetail {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int bookid;
private String category;
public Bookdetail() {
}
public Bookdetail(String category) {
this.category=category;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
}
Every object created just with Bookdetail detail=new Bookdetail(); is a detached entity which mean he's not in the hibernate session or don't have a identifier id.
And you have to wrap your services which persist or update or delete into a transaction.
So first you must save detail your detached entity : bookdetailRepository.save(detail) to attached it to the session But in your case you already specify cascade = {CascadeType.PERSIST,CascadeType.MERGE}
#ManyToOne(cascade = {CascadeType.PERSIST,CascadeType.MERGE})
private Bookdetail bookdetail;
No need to save it before, the job will be done automatically.
the solution then is to annotate the method run() with #Transactional
...
#Transactional
public void run(String... arg0)
...
bookdetailRepository.save(detail)
book1.setBookdetail(detail);
...
#Transactional before public void run() worked for me... Thank you very much #Youssef and #Chathuranga Tennakoon for your efforts. Sharing the code for others.
#SpringBootApplication
// #EnableJpaRepositories(basePackages = {"com.jpa.repository"})
public class HelloJpaApplication implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(HelloJpaApplication.class);
#Autowired
private BookRepository bookRepository;
// #Autowired
// private BookDetailRepository bookDetailRepository;
#Transactional
public void run(String... arg0) throws Exception {
Book book1 = new Book();
book1.setName("my thoughts");
Book book2 = new Book();
book2.setName("your thoughts");
Bookdetail detail = new Bookdetail();
detail.setCategory("good books");
// bookDetailRepository.save(detail);
book1.setBookdetail(detail);
book2.setBookdetail(detail);
bookRepository.save(book1);
bookRepository.save(book2);
}
public static void main(String[] args) {
SpringApplication.run(HelloJpaApplication.class, args);
}
}
#Entity
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int bookid;
private String name;
#ManyToOne(cascade = CascadeType.ALL)
private Bookdetail bookdetail;
public Book() {
}
public Book(String name) {
this.name = name;
}
public int getBookid() {
return bookid;
}
public void setBookid(int bookid) {
this.bookid = bookid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Bookdetail getBookdetail() {
return bookdetail;
}
public void setBookdetail(Bookdetail bookdetail) {
this.bookdetail = bookdetail;
}
}
#Entity
#Table(name = "bookdetail")
public class Bookdetail {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int bookid;
private String category;
public Bookdetail() {
}
public Bookdetail(String category) {
this.category = category;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
}
#Repository
public interface BookRepository extends JpaRepository<Book,Integer>{
}
Add BookdetailRepository as follows.
#Repository
public interface BookdetailRepository extends JpaRepository<Bookdetail,Integer> {
}
And change the HelloJpaApplication as follows.
#SpringBootApplication
#EnableJpaRepositories(basePackages = {"com.jpa"})
public class HelloJpaApplication implements CommandLineRunner{
private static final Logger logger = LoggerFactory.getLogger(HelloJpaApplication.class);
#Autowired
private BookRepository bookRepository;
#Autowired
BookdetailRepository bookdetailRepository;
public void run(String... arg0) throws Exception {
Book book1=new Book();
book1.setName("my thoughts");
Book book2=new Book();
book2.setName("your thoughts");
Bookdetail detail=new Bookdetail();
detail.setCategory("good books");
bookdetailRepository.save(detail); //saving the bookDetail
book1.setBookdetail(detail);
book2.setBookdetail(detail);
bookRepository.save(book1);
bookRepository.save(book2);
}
public static void main(String[] args) {
SpringApplication.run(HelloJpaApplication.class, args);
}
}
I have the following code which attempts to save a POJO object (Actor) into MongoDB using Spring Mongo Repository, but the repository object is always Null. I have followed multiple examples but mainly this one
The POJO class:
#Document(collection = "actors")
public class Actor
{
#Id
private String id;
...
//constructor
//setters & getters
}
The repository:
public interface ActorRepository extends MongoRepository<Actor, String>
{
public Actor findByFNameAndLName(String fName, String lName);
public Actor findByFName (String fName);
public Actor findByLName(String lName);
}
The service that uses the repository:
#Service
public class ActorService
{
#Autowired
private ActorRepository actorRepository;
public Actor insert(Actor a)
{
a.setId(null);
return actorRepository.save(a);
}
}
And I access the service from a REST controller class:
#RestController
public class Controllers
{
private static final Logger logger = Logger.getLogger(Controllers.class);
private static final ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringMongoConfig.class);
private ActorService actorService = new ActorService();
#RequestMapping(value="/createActor", method=RequestMethod.POST)
public #ResponseBody String createActor(#RequestParam(value = "fName") String fName,
#RequestParam(value = "lName") String lName,
#RequestParam(value = "role") String role)
{
return actorService.insert(new Actor(null,fName,lName,role)).toString();
}
...
}
The error that I get is NullPointerException from this line: return actorRepository.save(a); in the ActorService.insert() method.
Any Idea why is this happening?
EDIT: Here is the Spring Configurations
#Configuration
public class SpringMongoConfig extends AbstractMongoConfiguration
{
#Bean
public GridFsTemplate gridFsTemplate() throws Exception
{
return new GridFsTemplate(mongoDbFactory(), mappingMongoConverter());
}
#Override
protected String getDatabaseName()
{
return "SEaaS";
}
#Override
#Bean
public Mongo mongo() throws Exception
{
return new MongoClient("localhost" , 27017 );
}
public #Bean MongoTemplate mongoTemplate() throws Exception
{
return new MongoTemplate(mongo(), getDatabaseName());
}
}
The problem is that you are not using Spring to get the ActorService dependency -instead you have manually instantiated the dependency using
private ActorService actorService = new ActorService();.
The following code is the easiest fix in order to inject the ActorService dependency into the controller.
#RestController
public class Controllers
{
private static final Logger logger = Logger.getLogger(Controllers.class);
private static final ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringMongoConfig.class);
#Autowired
private ActorService actorService;
#RequestMapping(value="/createActor", method=RequestMethod.POST)
public #ResponseBody String createActor(#RequestParam(value = "fName") String fName,
#RequestParam(value = "lName") String lName,
#RequestParam(value = "role") String role)
{
return actorService.insert(new Actor(null,fName,lName,role)).toString();
}
...
}