I want to select rows that have a distinct email, see the example table below:
+----+---------+-------------------+-------------+
| id | title | email | commentname |
+----+---------+-------------------+-------------+
| 3 | test | rob#hotmail.com | rob |
| 4 | i agree | rob#hotmail.com | rob |
| 5 | its ok | rob#hotmail.com | rob |
| 6 | hey | rob#hotmail.com | rob |
| 7 | nice! | simon#hotmail.com | simon |
| 8 | yeah | john#hotmail.com | john |
+----+---------+-------------------+-------------+
The desired result would be:
+----+-------+-------------------+-------------+
| id | title | email | commentname |
+----+-------+-------------------+-------------+
| 3 | test | rob#hotmail.com | rob |
| 7 | nice! | simon#hotmail.com | simon |
| 8 | yeah | john#hotmail.com | john |
+----+-------+-------------------+-------------+
Where I don't care which id column value is returned. What JPA Specification can be used for this?
Here is the specification class,
public class UserSearchSpecification implements Specification<UserSearch> {
private static final Logger LOGGER = LogManager.getLogger(UserSearchSpecification.class);
private static final long serialVersionUID = 1L;
private List<SearchCriteria> list;
/**
* Custom JPA Specification Constructor
*
*/
public UserSearchSpecification() {
this.list = new ArrayList<>();
}
public void add(SearchCriteria criteria) {
list.add(criteria);
}
#Override
public Predicate toPredicate(Root<UserSearch> root, CriteriaQuery<?> arg1, CriteriaBuilder builder) {
//create a new predicate list
List<Predicate> predicates = new ArrayList<>();
//add add criteria to predicates
for (SearchCriteria criteria : list) {
if (criteria.getOperation() == (SearchOperation.GREATER_THAN)) {
predicates.add(builder.greaterThan(
root.get(criteria.getKey()), criteria.getValue().toString()));
} else if (criteria.getOperation() == (SearchOperation.LESS_THAN)) {
predicates.add(builder.lessThan(
root.get(criteria.getKey()), criteria.getValue().toString()));
} else if (criteria.getOperation() == (SearchOperation.GREATER_THAN_EQUAL)) {
predicates.add(builder.greaterThanOrEqualTo(
root.get(criteria.getKey()), criteria.getValue().toString()));
}
}
return builder.and(predicates.toArray(new Predicate[0]));
}
}
I hate JPAs Criteria API to much to produce actual code, but the basic idea is that you create the following construct:
from MyEntity e
where e.id in (
select min(id)
from MyEntity e2
where e.email = e2.email
)
This should be expressible with the Criteria API and therefore with Spring Data JPA Specifications.
Related
I am working on a simple personal project in order to learn SpringBoot.
Project Overview: Spring Boot aplication that gets data and inserts data through RestController endpoint into a MariaDb instance on a docker.
My MariaDb table is this:
\+------------------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
\+------------------+------------+------+-----+---------+----------------+
| ACCOUNT_ID | int(11) | NO | PRI | NULL | auto_increment |
| ACCOUNT_VALUE | int(11) | YES | | 0 | |
| ACCOUNT_CURRENCY | varchar(5) | YES | | NULL | |
\+------------------+------------+------+-----+---------+----------------+
My table has data (I inserted it manualy from the terminal):
MariaDB \[dev\]\> select \* from CUSTOMER_ACCOUNT;
\+------------+---------------+------------------+
| ACCOUNT_ID | ACCOUNT_VALUE | ACCOUNT_CURRENCY |
\+------------+---------------+------------------+
| 1 | 300 | RON |
| 2 | 300 | RON |
| 3 | 300 | RON |
| 4 | 300 | RON |
| 5 | 300 | RON |
| 6 | 300 | RON |
| 7 | 300 | RON |
| 8 | 300 | RON |
| 9 | 300 | RON |
| 10 | 300 | RON |
| 11 | 300 | RON |
| 12 | 300 | RON |
| 13 | 300 | RON |
| 14 | 300 | RON |
| 15 | 300 | RON |
| 16 | 300 | RON |
| 17 | 300 | RON |
| 18 | 300 | RON |
\+------------+---------------+------------------+
My Entity class is this:
#Entity
#Table(name = "CUSTOMER_ACCOUNT", schema = "dev", catalog = "")
public class CustomerAccountEntity {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
#Column(name = "ACCOUNT_ID")
private int accountId;
#Basic
#Column(name = "ACCOUNT_VALUE")
private Integer accountValue;
#Basic
#Column(name = "ACCOUNT_CURRENCY")
private String accountCurrency;
public int getAccountId() {
return accountId;
}
public void setAccountId(int accountId) {
this.accountId = accountId;
}
public Integer getAccountValue() {
return accountValue;
}
public void setAccountValue(Integer accountValue) {
this.accountValue = accountValue;
}
public String getAccountCurrency() {
return accountCurrency;
}
public void setAccountCurrency(String accountCurrency) {
this.accountCurrency = accountCurrency;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CustomerAccountEntity that = (CustomerAccountEntity) o;
return accountId == that.accountId && Objects.equals(accountValue, that.accountValue) && Objects.equals(accountCurrency, that.accountCurrency);
}
#Override
public int hashCode() {
return Objects.hash(accountId, accountValue, accountCurrency);
}
}
My repository class is this :
#Repository
public interface CustomerAccountRepository extends JpaRepository\<CustomerAccountEntity,Long\> {
#Query("Select ca.accountId, ca.accountValue, ca.accountCurrency from CustomerAccountEntity ca")
List<CustomerAccountEntity> findAllCA();
}
My settings is this:
# connect via localhost on port 3306
spring.datasource.url=jdbc:mariadb://localhost:3306/dev
spring.datasource.username=root
spring.datasource.password=mypass
spring.jpa.properties.hibernate.globally_quoted_identifiers=true
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.datasource.driver=cdata.jdbc.mariadb.MariaDBDriver
When I hit the controller to retrieve all the data it returns empty in browser.
I attempted to play with the settings to change the driver and hibernate dialect. Note: I do have a succesfull conection to the DB
When I start the aplication there pops up a new table in mariadb
| Tables_in_dev |
+------------------+
| CUSTOMER_ACCOUNT |
| customer_account |
+------------------+
2 rows in set (0.000 sec)
Hibernate calls goes to the lower case ones, but this one is empty. It seems it is deleted when I stop the app.
I am still learning... but can someone explain what is happening.
spring.jpa.hibernate.ddl-auto=create-drop;
having this in your properties file will drop all existing content in your table each time you restart or run your spring-boot application.
To keep all records in your tables change to spring.jpa.hibernate.ddl-auto=update;
Your repo has Long as id type while entity has int. Try to make them both of the same type Long
To avoid data loss while using mariadb in docker, you should create a docker volume and plug it to /var/lib/mysql/data when you create your container
I have three tables topics, category(HowtoCategory::class), sections(Howto::class).
Multiple categories belong to one topic and multiple sections belong to one category.
Topic Has Many Category
Category Has Many Sections
Category Belongs to Topic
Sections Belongs To Category
Topic Has Many Sections Through Category
I want to search through the sections(Howto::class) but display the related topics. How can I access topics in the controller from my query search of sections? I dont want to access it in the view but in the controller.
Controller
public function index(Request $request)
{
if (request()->filled('search')) {
request()->fullUrlWithQuery(['search ' => null]);
$sections = Howto::search($request->search)->get();
$request = $request->input('search');
} else {
$sections = null;
}
return view('howto-pages.howto-main', [
'sections' => $sections,
]);
}
Models
class Topic extends Model
{
public function categories()
{
return $this->hasMany(HowtoCategory::class);
}
public function sections()
{
return $this->hasManyThrough(Howto::class, HowtoCategory::class);
}
}
class HowtoCategory extends Model
{
public function sections()
{
return $this->hasMany(Howto::class);
}
public function topic()
{
return $this->belongsTo(Topic::class);
}
}
class Howto extends Model
{
public function category()
{
return $this->belongsTo(HowtoCategory::class);
}
}
Tables
mysql> desc howto;
+-------------------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+-----------------+------+-----+---------+----------------+
| id | bigint unsigned | NO | PRI | NULL | auto_increment |
| title | varchar(255) | NO | | NULL | |
| body | mediumtext | YES | | NULL | |
| description | mediumtext | YES | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
| section_id | int | YES | | NULL | |
| howto_category_id | bigint unsigned | YES | MUL | NULL | |
+-------------------+-----------------+------+-----+---------+----------------+
mysql> desc howto_category;
+----------------------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------------+-----------------+------+-----+---------+----------------+
| id | bigint unsigned | NO | PRI | NULL | auto_increment |
| howto_category_title | varchar(255) | NO | | NULL | |
| icon | varchar(255) | YES | | NULL | |
| description | mediumtext | YES | | NULL | |
| intro | text | YES | | NULL | |
| category_id | int | YES | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
| topic_id | bigint unsigned | YES | MUL | NULL | |
+----------------------+-----------------+------+-----+---------+----------------+
mysql> desc topics;
+-------------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-----------------+------+-----+---------+----------------+
| id | bigint unsigned | NO | PRI | NULL | auto_increment |
| topic_title | varchar(255) | NO | | NULL | |
| description | mediumtext | YES | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
+-------------+-----------------+------+-----+---------+----------------+
First and foremost i'd define the foreignIDS in the relations just to make sure that are working as expected.
class Topic extends Model
{
public function categories()
{
return $this->hasMany(HowtoCategory::class);
}
public function sections()
{
return $this->hasManyThrough(
Howto::class, HowtoCategory::class,
"topic_id", "howto_category_id"
);
}
}
then you can get the Topics by searching the sections like so
use Illuminate\Database\Eloquent\Builder;
$topics = Topic::whereHas("sections", function(Builder $query) {
$query->where("howto.description", "something to search")
})->get();
I have discovered a In my opinion a strange behaver from jpa. First here is the a simplified version of my code ( If I missed something let me know)
#Data
#Entity
#Table(name = "parent")
Parent {
#Id
#GeneratedValue
#Column(name = "id")
private Long id;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(
name = "parent_child_mapping",
joinColumns = #JoinColumn(name = "parent_id"),
inverseJoinColumns = #JoinColumn(name = "Child_id"))
private Set<Child> childs;
#Column(name = "name", unique = true, nullable = false)
private String name;
}
#Data
#Entity
#Table(name = "child")
Child {
#Id
#GeneratedValue
#Column(name = "id")
private Long id;
#ManyToMany(mappedBy = "keywords")
private Set<Parent> parents;
#Column(name = "name", unique = true, nullable = false)
private String name;
public Child() {
this.set = new HashSet<>();
}
public Child(String name, Parent p) {
this();
this.name = name;
this.parents.add(p);
}
public Child(String name, Set<Parent> parents) {
this();
this.name = name;
this.parents.addAll(parents);
}
}
interface ChildRpo extends extends JpaRepository<Child , Long> {
Optional<Child> findByName(String name);
}
interface ParentRepo extends extends JpaRepository<Parent, Long> {
Optional<Parent> findByName(String name);
}
DB:
| Parent | | parent_child_mapping | | Child |
| id | name | | child_id | parent_id | | id | name |
| 1 | pa1 | | 1 | 1 | | 1 | child1 |
| 2 | pa2 | | 2 | 1 | | 2 | child2 |
Parent p1 = ParentRepo.findById(1).get() // Parent has Child 1 & 2
Parent p2 = ParentRepo.findById(2).get() // Parent has no Childs
So now I get Data from a Rest Interface to add p2 a probably new child with a name
#Autowired
ChildRpo childRepo;
#Autowired
ParentRepo parentRepo;
#PostMapping("/example/{parentName}/{childName}")
public void add(#PathVar("parentName") String pName,
#PathVar("childName") String cName) {
// Here is the Problem I think
Parent p = parentRepo.findByName(pName).get();
p.getChilds.add(new Child(cName,p);
this.parentRepo.save(p);
}
Case1: pName = pa2 & cName = child3 (works as expected)
| Parent | | parent_child_mapping | | Child |
| id | name | | child_id | parent_id | | id | name |
| 1 | pa1 | | 1 | 1 | | 1 | child1 |
| 2 | pa2 | | 2 | 1 | | 2 | child2 |
| 3 | 2 | | 3 | child3 |
Case2: pName = pa2 & cName = child1 (not quite what I expected) but how it is
| Parent | | parent_child_mapping | | Child |
| id | name | | child_id | parent_id | | id | name |
| 1 | pa1 | | 1 | 1 | | 1 | child1 |
| 2 | pa2 | | 2 | 1 | | 2 | child2 |
| 3 | 2 | | 3 | child1 |
Case3: pName = pa2 & cName = child1 what I expected but how it isn't is
| Parent | | parent_child_mapping | | Child |
| id | name | | child_id | parent_id | | id | name |
| 1 | pa1 | | 1 | 1 | | 1 | child1 |
| 2 | pa2 | | 2 | 1 | | 2 | child2 |
| 1 | 2 |
I have to issues with this:
I thought that the unique attribute make a column unique so two strings with the same Content can't co exists in this row 'name' but It works. I would expect In case2 an exception.
And the second one:
How can I configuration it that it works like I wants them to work?
Do I have to load childOne's id? and Attach them to the new child1 entity?
I thought because the filed is unique that spring can decide:
The field isn't in the column so I add a new child.
The field is in the column so I will not create a new child instead I attach the parent to the old child with identical name. - but this isn't the way it works by default - is there a way that it works like described?
P.S:
Here is the domain Model from the current code:
Book -- oneToMany --> Page -- oneToMany --> Version -- ManyToOne --> Author
Book -- oneToMany --> Locations
..
How can I add 'Quick' a new Version to page with id = 5 and author with Id = 4 without loading all relationships.
EDIT: The Database is created by hibernate and here are the properties:
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto= update
Unique attribute in a #column works only if the database was created automatically by JPA provider. Let the provider to recreate your db, if possible. You can do this from the properties.
spring.jpa.hibernate.ddl-auto=create-drop
You have to use a method in your jpa interface which finds (or not) a child using the name. Then check a result and create a new child, if it doesn't exist yet. This should work fine:
Parent p = parentRepo.findByName(pName).get();
Child newChild;
Optional<Child> result = childRpo.findByName(cName);
if(result != null) {
newChild = result.get();
}
else {
newChild = new Child(cName);
}
p.getChilds.add(newChild);
newChild.getParents().add(p);
parentRepo.save(p);
Book
----------------------------
| id | name | published |
----------------------------
| 1 | book1 | 1 |
| 2 | book2 | 1 |
| 3 | book3 | 0 |
Chapter
----------------------------
| id | book_id | name | published |
----------------------------
| 1 | 1 | chapter1 | 1 |
| 2 | 1 | chapter2 | 0 |
| 2 | 2 | chapter1 | 0 |
| 3 | 3 | chapter1 | 1 |
class Book{
public function getChapter()
{
return $this->hasMany(Chapter::className(), ['kook_id' => 'id']);
} }
class Chapter{
public function getBook()
{
return $this->hasOne(Book::className(), ['id' => 'book_id']);
} }
How can i get published books with published pages using ActiveRecord (i want get book1 with chapter1 and book2 without any chapters)?
smth like Book::find($id)->where(['published' => 1])-> {{{left join with where}}} ->all())
ADDED
And then i wand to use it
echo $book->name;
foreach($book->getChapter() as chapter){
echo chapter->name;
}
Change your Book class relation as
class Book
{
public function getChapter()
{
return $this->hasMany(Chapter::className(), ['book_id' => 'id'])->where(['published' => 1]);
}
}
To get Related Records use with()
Book::find()->with(['chapter'])->where(['id' => $id ,'published' => 1])->all()
To Use it:
//Book Name
echo $book->name;
//For Chapters Name
if($book->chapter){
foreach($book->chapter as $chapter){
echo $chapter->name;
}
}
I have two tables in a database:
Grade
+--------+--------------+-----------------+
| int id | varchar name | varchar teacher |
+--------+--------------+-----------------+
| 1 | CL-2A | E. Wright |
| 2 | CL-2B | B. Springsteen |
+--------+--------------+-----------------+
Student
+--------+------------------+-----------+
| int id | varchar name | int grade |
+--------+------------------+-----------+
| 1 | Michael Wallings | 1 |
| 2 | Rowen Tress | 1 |
| 3 | Alys Harverd | 2 |
| 4 | Jeff Bass | 1 |
| 5 | Harry Farewell | 2 |
+--------+------------------+-----------+
As you've probably seen already, Student.grade refers to Grade.id with a foreign key.
Now I've made some entity objects for both tables. But how do I implement to relation between Student and Grade?
public class Student {
private int id;
private String name;
private int grade; // Keep the grade as an int.
...
or
public class Student {
private int id;
private String name;
private Grade grade; // Hold a reference to the corresponding entity object Grade.
...
or something?
The second code snippet requires me to also get the grade entity object derived from the database.