Is it possible to implement JPA-repository with filtering by lob-column?
I have the following code:
#Entity
#Table(name = "SUBJECT_IDENTIFIER")
public class SubjectIdentifier implements Serializable {
#Id
#Column(name = "SUBJECT_IDENTIFIER_ID")
private long subjectIdentifierIid;
#Lob
#Column(name = "SOR_BP_GUID", columnDefinition="BLOB NOT NULL")
private byte[] bpGuid;
//getter/setter
}
public interface SubjectIdentifierRepository extends JpaRepository<SubjectIdentifier, Long> {
#Query("select si from SubjectIdentifier si where si.bpGuid= :bpGuid")
SubjectRepository findByBpGuid(#Param("bpGuid") byte[] bpGuid);
}
//test
SubjectRepository byBpGuid = subjectIdentifierRepository.findByBpGuid("D9E70D24567E4DAE8FD3ED5898579092".getBytes());
but I can not find objects from database.
Do I have to implement this query by other way?
Sure, provided that your database supports it.
I recommend writing your query as shown below, as the requirement can be fully resolved without use of the #Query annotation.
SubjectRepository findOneByBpGuid(#Param("bpGuid") byte[] bs);
I'm a bit curious on the columnDefinition specification: is the db column set to the wrong type without that? I would prefer this statement over the use of columnDefinition if possible. This will leave the configuration database agnostic.
#Column(name = "SOR_BP_GUID", nullable = false)
See Also: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
Related
I am using the following class as an entity, and a controller class to write data on it:
#Entity
#Table(name = "TableA")
public class TableA {
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false, name="Id")
private BigInteger id;
#Column(nullable = false, name="Name")
private String name;
//Setters Getters
}
#RestController
public class TableAController {
#Autowired
TableARepository tableARepository;
#Transactional(rollbackFor = Exception.class)
#PostMapping(value="/CreateTableA")
public void createTableA(#RequestBody TableA newTableA){
TableA tableA = new TableA();
tableA = newTableA;
tableARepository.save(tableA);
}
}
The Id column value will be generated by the DB, so I used the #JsonProperty. But, when I test the REST API using the following as a request:
{
"name" : "Leo Messi"
}
I am getting the aforementioned error message. I have also tried the #JsonIgnore property with the same result. Is there a way to except the id property from the deserialization process? Or should I use another class dedicated the API Request? I am not comfortable with creating different models for every new API.
I am not sure if I should focus on resolving the error, or if I should design the classes using a Design Pattern that never produces it.
I have a table named 'content' which has a field named 'created_at'.
I am trying to use pageable and specifications in this table.
Specifications works perfectly but i have a problem with pageable. If i use the inherited method from the repository to search the pageable don't recognize the field with underscore and tries to split him. Givin this error:
"No property created found for type Content!"
If i create a method in the repository pageable works but specifications don't.
Here is my repository:
#Repository
public interface ContentRepository extends JpaRepository<Content,
String>,JpaSpecificationExecutor<Content> {
#Query(value = "SELECT * FROM content", nativeQuery = true)
public Page<Content> findAll(Specification<Content> specification, Pageable pageable);
}
How can i do both?
Content class:
#Entity
#Table(name = "content")
#Setter
#Getter
public class Content {
#Id
#Column(name = "id")
private String id;
#Column
private String name;
#Column
private String description;
#Column(columnDefinition = "TEXT")
private String content;
#Column(columnDefinition = "TEXT")
private String reference;
#ManyToOne
#JoinColumn(nullable = false)
private User author;
#ManyToOne
#JoinColumn(nullable = true)
private Agenda agenda;
#ManyToOne
#JoinColumn(nullable = false)
private ContentType contenttype;
#Column(columnDefinition = "boolean default true")
private boolean enabled;
#Column(columnDefinition = "boolean default false")
private boolean approved;
#Column
private Date sent_at;
#Column
private Date created_at;
#Column
private Date updated_at;
#Column
private Date deleted_at;
}
Avoid using underscores in the entity property names if you have control over the property naming. This will resolve your repository woes, and will result in a cleaner code-base. Developers dealing with the code after you will thank you.
Note, it's not just my opinion: Spring specifically discourages using underscores.
As we treat underscore as a reserved character we strongly advise to
follow standard Java naming conventions (i.e. not using underscores in
property names but camel case instead).
this JIRA issue shows why the documentation was updated with this reccomendation, and the part describing the double underscore option were removed.
I suspect your root problem is that Spring/Hibernate is not mapping camel case property names to the snake case names you have for your columns in the database. What you really need is for your property name to be interpreted in the SQL that hiberate generates as created_at.
Is that why underscores in your property name are "required"? If so, there are a few solutions:
Option 1: #Column annotation
To get JPA/Hibernate to map to the correct column names you can tell it the names explicitly. Use the annotation #Column(name="...") to tell it what column names to use in SQL. Then the field names are not constrained by the column names.
#Entity
#Table(name = "content")
#Setter
#Getter
public class Content {
#Id
#Column(name="created_at")
private String createdAt;
}
Option 2: Improved Naming Strategy
Or if your application has a large number of entities, rather than adding #Column to every property, change the default naming strategy in your configuration file to the hibernate improved naming strategy.
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
This naming strategy will convert camelCase to SNAKE_CASE. Then your class could look as simple as this:
#Entity
public class Content{
#Id
private String createdAt;
}
Using either of those options, when it creates the SQL it will resolve the column names to:
created_at
Note: If you are using, or can use Spring Boot, the auto-configuration default will use SpringNamingStrategy, which is a slightly modified version of the hibernate improved strategy. You won't have to do anything to get this improved naming strategy.
The finish line:
Using camel case in your property names you can write your repository method name using camel case, and you can stop trying to wrangle the double underscore:
#Repository
#Transactional
public interface ContentRepository extends CrudRepository<Content, String> {
#Query(value = "SELECT * FROM content", nativeQuery = true)
List<Student> findAll(Specification<Content> specification, Pageable pageable);
}
I have and oracle table defined this way:
MYCLASS_ID_PK (NUMBER(50,0)) |MYCLASS_UUID (VARCHAR2(50 BYTE)) |PAYLOAD (CLOB)
I have a JPARepository defined this way:
public interface MyClassRepository extends JpaRepository<MyClass, Long> {
Optional<MyClass> findByUuid(UUID uuid);
}
And I have an Entity defined this way:
...
import java.sql.Clob;
...
#Entity
#Table(name = "foo_myclass")
#NoArgsConstructor
#Getter
#Setter
#NamedStoredProcedureQueries({
#NamedStoredProcedureQuery(
name = "create_myclass",
procedureName = "CREATE_MYCLASS",
parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, name = "myClassUuid", type = String.class),
#StoredProcedureParameter(mode = ParameterMode.IN, name = "payload", type = Clob.class),
#StoredProcedureParameter(mode = ParameterMode.OUT, name = "myClassId", type = Long.class)
})
})
public class MyClass {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "myclass_id_pk")
private Long id;
#NotNull
#Type(type = "uuid-char")
#Column(name = "myclass_uuid")
private UUID uuid;
#Lob
#Column(name = "payload")
private Clob payload;
}
I initially used String in my Entity class to link to my payload to oracle CLOB. However, my string size is a bit too big to be properly handled (confirmed with unit test) by JPA and Oracle, the Clob approach allowed me to resolve this this specific issue.
The issue I face now is that my findByUuid(UUID uuid) return an instance of MyClass where my payload has been instantiated with the implementation of oracle.sql.CLOB instead of the org.hibernate.engine.jdbc.ClobProxy implementation.
At some point the oracle implementation of Clob causes me troubles and I would like to avoid it. I also want to use hibernate implementation to have something similar to what is described in https://thoughts-on-java.org/mapping-blobs-and-clobs-with-hibernate-and-jpa/.
Does anyone know how I can force the result of my findByUuid(UUID uuid) to use hibernate implemenation of Clob instead of the oracle implementation?
Finally, I realized that it is ok for me to work with the oracle.sql.CLOB. One of the reason I wanted to be able to recover an hibernate implementation of Clob is that following my findByUuid(UUID uuid) I was performing few operations on the returned MyClass and tried to saved it in my repository using the JPARepository save method (which resulted in a failure unless I converted the result to the Hibernate implementation of Clob).
I finally gave up trying to recover the hibernate implentation of Clob. And simply implemented a custom save method as shown in How to add custom method to Spring Data JPA (comment from kenny_k) where I convert the returned oracle.sql.CLOB to an instance of org.hibernate.engine.jdbc.ClobProxy before calling the JPA save method.
I use JPA for database access and annotated every column with the correct name. Now if I execute a query (e.g. findAll()) it returns
Unknown column 'program0_.program_id' in 'field list'
The error message is correct program_id is unknown because the real name is programId.
Models: Program
#Entity
#Table(name = "programs")
#XmlRootElement
public class Program implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "programId")
private Long programId;
#ManyToMany
#JoinTable(
name = "programlabels",
joinColumns = {
#JoinColumn(name = "program", referencedColumnName = "programId")},
inverseJoinColumns = {
#JoinColumn(name = "label", referencedColumnName = "labelId")})
private Collection<Label> labels;
}
Label
#Entity
#Table(name = "labels")
#XmlRootElement
public class Label implements Serializable {
#Id
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 100)
#Column(name = "labelId")
private String labelId;
}
Query
select program0_.program_id as program_1_5_, ...
Is there a reason why JPA changes "programId" to "program_id" or am I missing any configuration?
thanks
Edit: Oh sorry forgot to add query code/information.
I use the Spring Data's JpaRepository interface and tried the findAll() query.
#Repository
public interface ProgramRepository extends JpaRepository<Program, Long> {}
http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
spring.jpa.hibernate.naming.strategy is not a supported property for Spring JPA implementation using Hibernate 5.
Use the below property in application.properties
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
As described in spring-boot-jpa-column-name-annotation-ignored, your column name is being converted to snake case.
Possible solutions:
Setup a Naming Strategy
Use lowercase column names in your annotations
Were able to map
#Column(name = "PersonFullName")
private String PersonFullName;
to the database table column name "PersonFullName" without the underscore.
The below worked for me. Add this in the application settings and then use #Column to specify the physical database column name for the model's property.
#Column(name = "PersonFullName")
In Application.properties
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
use below in application.properties
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
I wanted to create bidirectional one to one relationship with shared primary key.
As it is stated here JPA Hibernate One-to-One relationship I have:
#Entity
public class UserProfileInformation {
#Id
#GeneratedValue(generator = "customForeignGenerator")
#org.hibernate.annotations.GenericGenerator(
name = "customForeignGenerator",
strategy = "foreign",
parameters = #Parameter(name = "property", value = "userEntity")
)
long id;
private long itemsPerPage;
#OneToOne(mappedBy="userProfileInformation")
private UserEntity userEntity;
...}
#Entity
#Table(name = "UserTable")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String publicName;
private String password;
private String emailAddress;
private String name;
private boolean active;
#OneToOne(cascade=CascadeType.ALL)
#PrimaryKeyJoinColumn
private UserProfileInformation userProfileInformation;
...}
now, when I try to persist my user in the database, I am getting org.hibernate.id.IdentifierGenerationException: null id generated for:class pl.meble.taboret.model.UserProfileInformation. Is it because, when userProfileInformation is persisted to the database userEntity doesn't have id generated at that point?
Also, what can I do to create bidirectional relationship with shared primary key in my example?
EDIT:
Requested code, this is simple controller to test the operation of persisting UserEntity.
#Controller
#RequestMapping("/test")
public class TestController {
#Autowired
UserDao userDao;
#RequestMapping(method= RequestMethod.GET)
public String t(Model model){
UserEntity entity=new UserEntity();
entity.setActive(false);
entity.setEmailAddress("a");
entity.setName("name");
entity.setPassword("qqq");
entity.setPublicName("p");
UserProfileInformation p = new UserProfileInformation(entity);
entity.setUserProfileInformation(p);
userDao.addUser(entity);
return "login";
}
}
I think the problem is with the id generation strategy. For hibernate #GeneratedValue(strategy = GenerationType.AUTO) translates into a native identifier generation. This means that hibernate expects an identity id field for the UserTable.
I don't know exactly how SQLite works in terms of identity columns, but it seems from this SO question is a little different (see the second answer).
Anyway if you plan to run your application on multiple databases is better for portability to change the id generation strategy from GenerationType.AUTO and use hibernate enhanced generators: SequenceStyleGenerator or TableGenerator. See this link in the hibernate documentation.
EDIT:
I tried to reproduce your problem, and it seems that SQLite dialect is not among the officially supported hibernate dialects. Meanwhile I tested your case with the H2 embeded database and it works as expected: your mappings are correct.
If you are using an unofficial SQLite dialect it might be a bug with this dialect.