while coding the Functional Test for my web app which uses playframework,I created
#Before
public void setup() {
Fixtures.deleteDatabase();
}
#Test
public void testListTagged() {
Fixtures.loadModels("data.yml");
Response response = GET("/books/category/science");
assertNotNull(renderArgs("books"));
List<Book> books = (List<Book>)renderArgs("books");
assertEquals(3,books.size());
the listTagged method checks the Cache for a map object (String:List<Book>)that contains a list of books belonging to the given category, and if the map is null or the list is null,database query is made and the list is rendered as 'books'.
public static void listTagged(String category) {
List<Book> books =null;
Map<String,List<Book>> tagMap = (Map<String, List<Book>>) Cache.get("tagmap");
if(tagMap!= null) {
books = tagMap.get(category);
}
if(tagMap==null || books == null) {
books= Book.findTaggedWith(category);
}
Book book = null;
if (books!=null && books.size()>0) {
book = books.get(0);
}
render(category,book, books);
}
Book class is
#Entity
public class Book extends Model implements Comparable<Book>{
#Required
#Column(unique = true)
public String isbn;
#Required
//#Field
public String name;
...
#ManyToMany(cascade=CascadeType.PERSIST)
public Set<Category> categories;
public Book(String isbn, String name, ...) {
super();
this.isbn = isbn;
this.name = name;
...
this.categories = new TreeSet<Category>();
}
...
public static List<Book> findTaggedWith(String categoryName) {
Map<String,List<Book>> tagMap = (Map<String, List<Book>>) Cache.get("tagmap");
if(tagMap==null) {
tagMap= new HashMap<String,List<Book>>();
}
List<Book> books = Book.find("select distinct book from Book book join book.categories as cat where cat.name=:name").bind("name", categoryName).fetch();
tagMap.put(categoryName, books);
Cache.add("tagmap", tagMap,"20mn");
return books;
}
Running the above test alone caused no problems.But when it was run along with some unit tests which called the database for various books causes a lazy initialization exception
A java.lang.RuntimeException has been caught, java.util.concurrent.ExecutionException: play.exceptions.TemplateExecutionException: failed to lazily initialize a collection of role: models.Book.categories, no session or session was closed
How do I solve this? can someone please advise?
you could use:
#ManyToMany(cascade=CascadeType.PERSIST, fetch=FetchType.EAGER)
public Set<Category> categories;
This will probably solve the problem. Otherwise you'll have to take care, that a session is open. Tested and now working
Related
I'm following the tutorial for play framework in Java (#13) on youtube and I stuck in Index Method of BookStore Application. I cant go any further since I get: "Ambiguous method call. Both render(Set,) in index and render(Set) in index$ match".
I tried to change Set for List but the massage I got was basically the same only in relation to List.
public class BooksController extends Controller {
public Result index(){
Set<Book> books = Book.allBooks();
return ok(index.render(books)); //<--------- the error
}
}
public class Book {
public Integer id;
public String title;
public Integer price;
public String author;
public Book(Integer id, String title,Integer price, String author){
this.id = id;
this.title = title;
this.price = price;
this.author = author;
}
public static Set<Book> books;
static {
books = new HashSet<>();
books.add(new Book(1, "Java", 20, "ABC"));
books.add(new Book(2,"C++", 30, "XYZ"));
}
public static Set<Book> allBooks(){
return books;
}
}
You get this error because index from views.html.index.render and your method index() in BooksController have same name and compiler is confused what method to use. Just change name of your render method to something else for example booksIndex() and your problem will be gone.
public class BooksController extends Controller {
public Result booksIndex(){
Set<Book> books = Book.allBooks();
return ok(index.render(books));
}
}
P.S. don't forget to change your routes file after
I know English badly, but i'm trying to describe my problem.
I'm new in Spring. And I have some problems with adding data to my database.I have to table Pc and Pc characteristics. They are related by Id. It's easy to add data in non realted table, but how can I add data in related table? What shoud I write in my Controller? There are some classes below.
Pc class:
#Entity
#Table(name = "pc")
public class Pc {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private int price;
public Pc(){}
public Pc(String name, int price) {
this.name = name;
this.price = price;
}
#OneToMany
#JoinColumn(name = "pc_id")
private List<PcChars> chars = new ArrayList<>();
public List<PcChars> getChars() {
return chars;
}
public void setChars(List<PcChars> chars) {
this.chars = chars;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
PcChars class:
#Entity
#Table(name = "pcChars")
public class PcChars {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private String value;
public PcChars(){}
public PcChars(String name, String value) {
this.name = name;
this.value = value;
}
#ManyToOne
private Pc pc;
public Pc getPc() {
return pc;
}
public void setPc(Pc pc) {
this.pc = pc;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
PcCharactsController:
#Controller
public class PcCharactsController {
final private PcRepo pcRepo;
final private PcCharRepo pcCharRepo;
public PcCharactsController(PcRepo pcRepo, PcCharRepo pcCharRepo) {
this.pcRepo = pcRepo;
this.pcCharRepo = pcCharRepo;
}
//Pc characteristics list
#GetMapping("pc/{id}/")
public String pcCharList(#PathVariable int id, Model model) throws Exception{
Pc pc = pcRepo.findById(id).orElseThrow(() -> new Exception("PostId " +
id + " not found"));
List<PcChars> pcChars = pc.getChars();
model.addAttribute("model", pc.getName());
model.addAttribute("pcChars", pcChars);
return "charList";
}
//add characteristic
#PostMapping("pc/{id}/")
public String addCharact(){
return "charList";
}
Characteristics.ftl:
<html>
<head>
<title>Ho</title>
</head>
<body>
<div>
<form method="post" action="/pc/${id}/">
<input type="text" name="name">
<input type="text" value="value">
<input type="hidden" name="pc_id" value="${id}">
<button type="submit">Add</button>
</form>
</div>
</body>
</html>
Since you are not using any modelAttribute to bind the input values straight to a POJO you can use simple HttpServletRequest to get the input attributes, use them to create the object you want to store and store it using Hibernate
#PostMapping("pc/{id}/")
public String addCharact(HttpServletRequest req){
String name = req.getParameter("name");
String value = req.getParameter("value");
String id = req.getParameter("id");
PcChars pcchars = new PcChars(name,value,id); // create the corresponding constructor
SessionFactory sessionFactory;
Session session = sessionFactory.openSession();
Transaction tx = null;
try{
tx = session.getTransaction();
tx.begin();
session.save(pcchars);
tx.commit();
}
catch (HibernateException e) {
if (tx!=null) tx.rollback();
e.printStackTrace();
} finally {
session.close();
}
return "charList";
}
The part of Spring you're using is called Spring data, a library that allows you to use JPA in your Spring application. JPA is a specification for frameworks called ORM (Object-relationnal mapping).
To keep it simple, in your code, you do not use the relational approach anymore, but an object approach. Annotations you put on your classes' fields are used to define mappings between them and your database tables and fields.
So, you don't have to insert both entities separately anymore. You need to create a Pc instance, then to create a PcChars one, and finally to add the chars into the pc's chars list, like this :
Pc myPc = new Pc();
PcChars myChars = new PcChars();
myPc.getChars().add(myChars);
And when you'll use your repository to save the modifications with this :
pcRepo.save(myPc);
The JPA implementation will automatically do the work for you :
Inserting the row corresponding to your PC instance in the PC table
Inserting the row corresponding to your PC chars in the the PC_CHARS table
Settings the PC_CHARS.PC_ID with the ID of the freshly inserted PC instance's id in order to create the reference between them.
Not sure, but I think the ORM also do this when you add your chars to the pc instance :
myChars.setPc(myPc);
in order to make the bound between both instances reciprocal.
Note that I used arbitrary field names according to your schema.
I strongly suggest you to give responsibility of relationship to child side when using #OneToMany relation.
Modify your parent class as below:
#OneToMany(cascade = CascadeType.ALL, mappedBy="pc")
#BatchSize(size = 10)
private List<PcChars> chars = new ArrayList<>();
public void addPcChar(PcChar pcChar) {
this.chars.add(pcChar);
pcChar.setPc(this);
}
On the child class:
#ManyToOne
#JoinColumn(name = "pc_id")
private Pc pc;
Now you can persist your parent with child as below :
Pc pc = new Pc();
PcChar pcChar = new PcChar();
pc.addPcChar(pcChar);
If you use spring boot data repository, it saves it correctly as below
// assume your repository like below
public interface PcRepository extends CrudRepository<Pc, Integer> {}
// in your service or whatever in some place
pcRepository.save(pc);
With saving hibernate entity manager:
EntityManagerFactory emfactory =
Persistence.createEntityManagerFactory("Hibernate");
EntityManager entitymanager = emfactory.createEntityManager();
entitymanager.getTransaction().begin();
entitymanager.persist(pc);
entitymanager.getTransaction().commit();
entitymanager.close();
emfactory.close();
For detailed information about hibernate relationship take a look at my post : https://medium.com/#mstrYoda/hibernate-deep-dive-relations-lazy-loading-n-1-problem-common-mistakes-aff1fa390446
Hi Spring and Hibernate experts!
Can any one say if it is possible to use SQL IN-clause in custom #Query in CrudRepository while the Arraylist or set of strings is passed as parameter?
I am relatively new to Spring and do not quite figure out why I get the following Spring error:
"java.lang.IllegalArgumentException: Parameter value [d9a873ed-3f15-4af5-ab1b-9486017e5611] did not match expected type [IoTlite.model.Device (n/a)]"
In this post (JPQL IN clause: Java-Arrays (or Lists, Sets...)?) the subject is discussed pretty closely but I cannot make the suggested solution to work in my case with custom #Query.
My demo repository as part of the spring boot restful application is the following:
#Repository
public interface DeviceRepository extends JpaRepository<Device, Long> {
#Query("SELECT d FROM Device d WHERE d IN (:uuid)")
List<Device> fetchUuids(#Param("uuid") Set<String> uuid);
}
And the model-class is the following:
#Entity
#SequenceGenerator(sequenceName = "device_seq", name = "device_seq_gen", allocationSize = 1)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Device implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "device_seq_gen")
#JsonIgnore
private Integer id;
#Column(unique=true, length=36)
#NotNull
private String uuid = UUID.randomUUID().toString();
#Column(name="name")
private String name;
#JsonInclude(JsonInclude.Include.NON_NULL)
private String description;
#OneToMany(
mappedBy="device",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Sensor> sensors = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#JsonIgnore
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDeviceUuid() {
return uuid;
}
public void setDeviceUuid(String deviceUuid) {
this.uuid = deviceUuid;
}
public List<Sensor> getSensors() {
return sensors;
}
public void addSensor(Sensor sensor){
sensor.setDevice(this);
sensors.add(sensor);
}
}
An here is the relevant part of the service calling the fetchUuids-custom-method with set-list of strings as parameter (service naturally being called by the relevant restcontroller):
#Service
public class DeviceService implements IDeviceService {
#Autowired
private DeviceRepository deviceRepository;
...
#Override
public List<Device> listDevices(Set<String> clientIds) {
return deviceRepository.fetchUuids(clientIds);
}
...
}
Quick fix
You have WHERE d IN (:uuid) in the custom query. You cannot match d, which is an alias for Device entity with :uuid parameter, which is a collection of Strings.
WHERE d.uuid IN (:uuid) would fix the query - it matches a String with Strings.
What you should do instead
It's rather misleading to name the method fetchUuids and return a list of Device instances. It's also unnecessary to write a custom query to do that. You can benefor from repository method name conventions and let Spring Data Jpa framework generate the query for you:
List<Device> findByUuidIn(Set<String> uuids);
You can write in this way
#Query(value = "select name from teams where name in :names", nativeQuery = true)
List<String> getNames(#Param("names") String[] names);
and call the function in service and pass an array of String as arguments.like this
String[] names = {"testing team","development team"};
List<String> teamtest = teamRepository.getNames(names);
Yes is possible to using collection in JPA query parameters.
Your query is wrong, it should be like this:
#Query("SELECT d FROM Device d WHERE d.uuid IN :uuid")
I have two classes of Authors and Books:
public class Authors extends RealmObject {
#PrimaryKey
private String url_base;
private RealmList<Books> books;
... getters & setters...
public RealmList<Books> getBooks() {
return books;
}
public void setBooks(RealmList<Books> books) {
this.books = books;
}
}
public class Books extends RealmObject {
#PrimaryKey
private String url_base;
private Authors author;
... getters & setters...
public Authors getAuthor() {
return author;
}
public void setAuthor(Authors author) {
this.author = author;
}
}
Perform inserts:
Authors author = new Authors();
author.setUrl_base("url_base")
Books book = new Books();
book.setUrl_base("lala");
book.setAuthor(author);
author.getBooks().add(book); // error comes here
realm.beginTransaction();
realm.copyToRealmOrUpdate(author);
realm.commitTransaction();
And the program gives java.lang.NullPointerException.
Error line : author.getBooks().Add (book);
What could be wrong? I ask your help.
You haven't set the books member for your author object (as books is not initialized it is null) so getBooks() will return a null.
Make sure books is initialized before using it :
public class Authors extends RealmObject {
#PrimaryKey
private String url_base;
private RealmList<Books> books = new RealmList<Books>(); // An empty unmanaged books list.
... getters & setters...
or use the setter method (though initializing it on construction will be more correct in this case):
Authors author = new Authors();
author.setUrl_base("url_base")
author.setBooks(new RealmList<Book>());
UPDATE:
As #AndreyAtapin correctly noted in the solution above the list will be unmanaged. If you want a managed list pass the required arguments as specified in the RealmList api but whatever you choose the books list must be initialized before you start manipulating it
According to Realm docs you should instantiate entity objects with special factory method:
realm.beginTransaction();
Authors author = realm.createObject(Authors.class);
author.setUrl_base("url_base")
Books book = realm.createObject(Books.class);
book.setUrl_base("lala");
book.setAuthor(author);
author.getBooks().add(book); // error comes here
realm.commitTransaction();
When you instantiate your entities with constructor, obviously all fields are null by default:
public class Authors extends RealmObject {
#PrimaryKey
private String url_base; // = null
private RealmList<Books> books; // = null
... getters & setters...
}
PS: By the way, using variables names url_base contradicts the Java coding conventions. You better call it urlBase (setUrlBase/getUrlBase accordingly).
Have to beans:
#Entity
#Table(name="book")
public class Book {
#Id
#Column(name="id_book")
#GeneratedValue(generator="increment")
#GenericGenerator(name="increment", strategy="increment")
private int id;
#Column
#Size(min=1,max=100)
private String title;
#Column
#Size(min=1,max=400)
private String description;
#Column
private Integer year=0;
#ManyToMany(cascade={CascadeType.ALL},fetch = FetchType.EAGER)
#Fetch (FetchMode.SELECT)
#JoinTable(name="book_author",
joinColumns={#JoinColumn(name="book_id_book")},
inverseJoinColumns= {#JoinColumn(name="author_id_author")})
private List<Author> author=new ArrayList<Author>();
//getters/setters
}
and:
#Entity
#Table(name="author")
public class Author {
#Id
#Column(name="id_author")
#GeneratedValue
private Integer id;
#Column
private String name;
#Column
private String surname;
#ManyToMany(mappedBy="author")
private Set<Book> book=new HashSet<Book>();
//getters/setters
}
In my jsp I'm have form for enter data about book, and multiple list for select author(s) from DB, problem only in select authors, therefore give only this code:
<sf:select multiple="true" path="author" items="${authors}" size="7" >
</sf:select>
Where ${authors} - List with objects Author from DB. Use POST request.
In my controller for this page have this (I know it's not correct):
#RequestMapping(value="/addbook", method=RequestMethod.POST)
public String addBook(Book book){
hibarnateService.saveBook(book);
return "redirect:/books";
}
When I'm create book without select authors, but enter another information, all fine, book save in DB. When select some authors get this - The request sent by the client was syntactically incorrect.
Problem solved by add in controller:
#InitBinder
protected void initBinder(WebDataBinder binder){
binder.registerCustomEditor(Author.class, new Editor(hibarnateService));
}
and create class:
public class Editor extends PropertyEditorSupport {
private final Dao hibernateService;
public Editor(Dao hibernateService){
this.hibernateService=hibernateService;
}
#Override
public void setAsText(String text) throws IllegalArgumentException{
Author author=hibernateService.getAuthor(Integer.parseInt(text));
setValue(author);
}
}
P.S. What wrong with me? I can't find the right answer myself until I ask here)
You will need to implement initBinder in your controller, below can be tentative code (not tested)
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(List.class, "authors ", new CustomCollectionEditor(List.class)
{
#Override
protected Object convertElement(Object element)
{
Long id = null;
if(element instanceof Long) {
//From the database 'element' will be a Long
id = (Long) element;
}
return id != null ? authorService.loadAuthorById(id) : null;
}
});
}