I am trying to test one of my services and it suddenly fails with the following exception.
I am trying to figure out what is causing this exception to be thrown:
Caused by: org.h2.jdbc.JdbcSQLException: Table "XYZ" not found; SQL statement:
insert into xyz (id, xx_id, yy_id, order, path, place_id, primary) values (null, ?, ?, ?, ?, ?, ?) [42102-183]
Connection:
Creating new JDBC Driver Connection to [jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false]
PersistenceContext used for testing:
#Configuration
public class PersistenceContext {
#Bean
public EmbeddedDatabase dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:sql/db-schema.sql")
.build();
}
}
db-schema.sql
CREATE TABLE xyz(
id int(11) NOT NULL,
xx_id int(11) NULL,
yy_id int(11) NULL,
path varchar(200) NOT NULL,
date_time_added datetime NOT NULL,
"order" int(11) DEFAULT NULL,
"primary" bit(1) DEFAULT NULL,
PRIMARY KEY (id),
CONSTRAINT fk_xx FOREIGN KEY (xx_id) REFERENCES xx (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT fk_yy FOREIGN KEY (yy_id) REFERENCES yy (id) ON DELETE NO ACTION ON UPDATE NO ACTION
);
The class where the error is thrown
private Image addXyzForXX(MultipartFile file, Xx xx) throws ... {
String destDir = resourceService.getPlacesUploadDir();
Xyz xyz = new Xyz();
xyz.setXx(xx);
String filePath = imageUploadService.upload(file, destDir);
java.util.Date dt = new java.util.Date();
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String currentTime = sdf.format(dt);
xyz.setPath(filePath);
xyz.setDateTimeAdded(currentTime);
xyz.setOrder(1);
xyz.setPrimary(true);
xyz = xyzRepository.save(xyz);
return xyz;
}
Xyz Repository
#Repository
public interface XyzRepository extends PagingAndSortingRepository<Xyz, Long> {
}
#Entity
#Table(name = "xyz")
public class Xyz{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String path;
private String dateTimeAdded;
private Integer order;
private Boolean primary;
#ManyToOne(optional=true)
#JoinColumn(name = "xxId")
private Xx xx;
[getters and setters for each field]
}
The testing class
#RunWith(SpringJUnit4ClassRunner.class)
#Transactional
public class ImageServiceTest {
#Autowired
private XyzService xyzService;
#Autowired
private XxService xxService;
#Test
public void testArgumentsNullity3() throws Exception {
XX xx = new XX("a", "b", "c", "d", "e");
xx= xxService.addXx(xx);
xyzService.addImage(XxService.Scope.XX, xx,new MockMultipartFile("a","a","image/jpeg", new byte[1024]));
}
}
I found the actual problem.
The exception message I showed in my initial post is missing something important. In the stacktrace there was something else that says "bad sql grammar". I was focused on "images" table because this was the first thing in the stack trace.
After I checked all the possible known issues, I tried to rename the fields from my entity because I thought it will H2 will interpret them as keywords.
After renaming the fields, everything worked just fine.
Related
I have a ResponseDto class which looks like as below:
public static class HealthGoalsHighlight {
#ApiModelProperty(value = "Total number of eligible users")
private Long totalEligibleUsers;
#ApiModelProperty(value = "Total number of registered users")
private Long totalRegisteredUsers;
#ApiModelProperty(value = "Total number of users with atleast one goal count")
private Long totalUsersWithGoal;
#ApiModelProperty(value = "Top goal name selected by user")
private String topGoal;
#ApiModelProperty(value = "Bottom goal name selected by user")
private String bottomGoal;
}
This DTO was made based upon below table structure:
health_goals
(
uid BIGSERIAL NOT NULL CONSTRAINT health_goals_pkey primary key,
employer_key bigint not null,
total_eligible_users bigint not null,
total_registered_users bigint not null,
total_users_with_goal bigint not null,
top_goal_name varchar(255),
bottom_goal_name varchar(255),
created_ts TIMESTAMP NOT NULL DEFAULT NOW(),
updated_ts TIMESTAMP NOT NULL DEFAULT NOW(),
created_by varchar(255),
updated_by varchar(255)
);
Now the table structure has been changed to as below:
health_goals
(
uid BIGSERIAL NOT NULL CONSTRAINT health_goals_pkey primary key,
employer_key bigint not null,
health_goals_metric_value json null,
created_ts TIMESTAMP NOT NULL DEFAULT NOW(),
updated_ts TIMESTAMP NOT NULL DEFAULT NOW(),
created_by varchar(255),
updated_by varchar(255)
);
Basically now all these columns like total_eligible_users, total_registered_users, total_users_with_goal, top_goal_name, bottom_goal_name wil be consolidated to single columnhealth_goals_metric_value as a JSON data type.
How can I write the response DTO for JSON data type column. Also what changes needs to be done in my AggMapper class.
Well one way is by using converter function. You can use the converter function to get values in same format.
Change your orm.xml something like below on your column definition
<basic name="healthGoalsMetricValue">
<column name="health_goals_metric_value" nullable="true"/>
<convert converter="path.to.your.HealthGoalsMetricValueConverter"/>
</basic>
Or if you have java file
aggentity will have following entry
#Convert(converter = HealthGoalsMetricValueConverter.class)
private HealthGoalsHighlight healthGoalsHighlight ;
and your class HealthGoalsMetricValue will look something like
//////////////////Edited converter class after comments
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.io.IOException;
#Converter
public class HealthGoalsMetricValueConverter implements AttributeConverter<HealthGoalsMetricValue, String> {
private final ObjectMapper mapper = new ObjectMapper();
//And then override like that
#Override
public String convertToDatabaseColumn(HealthGoalsHighlight healthGoalsMetricValue) {
try {
json = mapper.writeValueAsString(healthGoalsMetricValue);
} catch (JsonProcessingException exception) {
throw new JsonProcessingException("Error occurred while object serialization", exception);
}
return json;
}
//And then override again
#Override
public HealthGoalsMetricValue convertToEntityAttribute(String healthGoalsMetricValuestr ) {
HealthGoalsMetricValue healthGoalsMetricValue = null;
try {
if (healthGoalsMetricValue != null) {
healthGoalsMetricValue = mapper.readValue(healthGoalsMetricValuestr, HealthGoalsMetricValue.class);
}
} catch (Exception exception) {
throw new Exception("Error occurred while object Deserialization", exception);
}
return healthGoalsMetricValue;
}
This all will do the job for you.
If you can add additional library have a look at https://github.com/vladmihalcea/hibernate-types project, it's super easy with that.
With this library you will end up with code as simple as this
#Entity
#Table(name = "health_goals")
#TypeDefs({
#TypeDef(name = "json", typeClass = JsonStringType.class),
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
})
public class HealthGoal {
// all other columns
#Type(type = "json")
private HealthGoalsHighlight healthGoalsHighlight;
// getters setters
}
And if using the maven add dependency
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>2.9.10</version> // or newer version
</dependency>
I'm working with spring-boot-starter-data-jpa. Should I use annotation #GeneratedValue on my entity id if my code working without it and generate PRIMARY KEY automatically in mysqldb?
When I run the test in the sqltable appears new row with an ID with the following AUTO_INCREMENT value, while passed every time id 0.
Entity
#Data
#Entity
#RequiredArgsConstructor
#NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
public class Person {
#Id
// #GeneratedValue(strategy = GenerationType.IDENTITY)// use or not - the same effect
private int id;
#NonNull
private String name;
#NonNull
private String surname;
}
Repository
public interface PersonRepository extends CrudRepository<Person, Integer> {
Person findByNameAndSurname(String name, String surname);
}
Testing
#RunWith(SpringRunner.class)
#SpringBootTest
public class SpringRestInventoryPersistenceTests {
#Autowired
private PersonRepository personRepository;
#Test
public void personPersist() {
Person person = new Person("John", "Smith");
assertTrue(person.getId() == 0);
personRepository.save(person);
assertTrue(person.getId() == 0);
Person person2 = personRepository.findByNameAndSurname("John", "Smith");
assertEquals(person.getName(), person2.getName());
}//test passed
mySql table
CREATE TABLE `person` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`surname` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8
I was also facing the same issue , I have added sequence generator in DB say 'idGenerator' ,and then add
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator="idGenerator")
#SequenceGenerator(name='dbname' , sequenceName="idGenerator") ,
This will take the values from sequence generator created in DB
I`ve understood the reason for this behavior. Without the #GeneratedValue annotation, ID is not generated automatically and ID with value 0 is always passed to the mysql database. In this case mysql generate ID value from AUTO_INCREMENT. This is default behavior.
To disable this behavior you can set next parameter:
SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'
Then after second call test method personPersist() we get error. In this case we can`t generate ID in mysql DB without #GeneratedValue annotation.
#Data
#MappedSuperclass
public class BaseModel implements Serializable {
private static final Long serialVersionUID = -1442801573244745790L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
#Convert(converter = LocalDateTimeConverter.class)
private LocalDateTime createAt = LocalDateTime.now();
#JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
#Convert(converter = LocalDateTimeConverter.class)
private LocalDateTime updateAt = LocalDateTime.now();
}
#Data
#Entity
#Table(name = "tb_vip_code")
#SQLInsert(sql = "insert ignore into tb_vip_code (code, duration) values (?, ?)")
public class VipCode extends BaseModel {
private static final Long serialVersionUID = -4697221755301869573L;
private String code;
private Integer duration;
private Integer status = 0;
private Long userId;
public VipCode() {}
}
#Test
public void addOne() throws Exception {
VipCode vipCode = new VipCode();
vipCode.setCode("123456");
vipCode.setDuration(1);
service.addOne(vipCode);
}
create table tb_vip_code
(
id bigint auto_increment
primary key,
code varchar(20) not null,
status int default '0' not null,
user_id bigint null,
duration int not null,
create_at datetime default CURRENT_TIMESTAMP not null,
update_at timestamp default CURRENT_TIMESTAMP not null,
constraint tb_vip_code_code_uindex unique (code)
);
All code as above, I am trying to use a custom sql to save object, but it throws an exception:
Caused by: java.sql.SQLException: Parameter index out of range (3 > number of parameters, which is 2).
I only have two parameters; why does it say it needed three?
The SQL statement specified by #SQLInsert gets the fields of your entity bound as parameters.
You have at least 4 attributes in your VipCode entity directly, and I suspect even more from the BaseModel class it inherits from. But your SQL statment expects only two parameters.
To fix the problem remove the superfluous attributes from the entity, add parameters for them to the query or mark them as #Transient.
I have 2 classes extending a base class.
Questions.java
#Entity
#Table(name="question")
#Access(value = AccessType.FIELD)
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Questions{
private static final long serialVersionUID = 1L;
private String qid;
#Column(name="addedtime")
private String addedtime;
#Column(name="qlang")
private String qlang;
#Id
#Column(name="qid")
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Access(value = AccessType.PROPERTY)
public String getQid() {
return qid;
}
public void setQid(String qid) {
this.qid = qid;
}
#Access(value = AccessType.PROPERTY)
public String getAddedtime() {
return addedtime;
}
public void setAddedtime(String addedtime) {
this.addedtime = addedtime;
}
#Access(value = AccessType.PROPERTY)
public String getQlang() {
return qlang;
}
public void setQlang(String qlang) {
this.qlang = qlang;
}
}
MCQ.java, TwoMarkQ.java - all 2 classes extend Question.java.
MCQ.java
#Entity
#Table(name="MCQ")
#Access(value = AccessType.FIELD)
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class MCQ extends Questions implements Serializable{
#Column(name="option_1")
#Access(value = AccessType.FIELD)
private String option_1;
#Access(value = AccessType.PROPERTY)
public String getOption_1() {
return option_1;
}
public void setOption_1(String option_1) {
this.option_1 = option_1;
}
#Column(name="option_2")
#Access(value = AccessType.FIELD)
private String option_2;
#Access(value = AccessType.PROPERTY)
public String getOption_2() {
return option_2;
}
public void setOption_2(String option_2) {
this.option_2 = option_2;
}
}
TwoMarkQ.java
#Entity
#Table(name="TwoMarkQ")
#Access(value = AccessType.FIELD)
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class TwoMarkQ extends Questions implements Serializable{
#Column(name="option_1")
#Access(value = AccessType.FIELD)
private String option_1;
#Access(value = AccessType.PROPERTY)
public String getOption_1() {
return option_1;
}
public void setOption_1(String option_1) {
this.option_1 = option_1;
}
#Column(name="option_2")
#Access(value = AccessType.FIELD)
private String option_2;
#Access(value = AccessType.PROPERTY)
public String getOption_2() {
return option_2;
}
public void setOption_2(String option_2) {
this.option_2 = option_2;
}
}
All these 3 tables are mapped to unique tables in MySQL database.
Following are the results for show create table for each table
create table `question` (
`qid` varchar(48) COLLATE utf8mb4_unicode_ci NOT NULL,
`addedtime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
`qtype` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`qlang` varchar(48) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`qid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
create table `MCQ`(
`qid` varchar(48) COLLATE utf8mb4_unicode_ci NOT NULL,
`option_1` bigint(20) DEGAULT `0`,
`option_2` bigint(20) DEGAULT `0`,
PRIMARY KEY (`qid`),
CONSTRAINT `mcq_ibfk1` FOREIGN KEY (`qid`) REFERENCES `question` (`qid`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
create table `TwoMarkQ`(
`qid` varchar(48) COLLATE utf8mb4_unicode_ci NOT NULL,
`option_1` bigint(20) DEGAULT `0`,
`option_2` bigint(20) DEGAULT `0`,
PRIMARY KEY (`qid`),
CONSTRAINT `two_markq_ibfk1` FOREIGN KEY (`qid`) REFERENCES `question` (`qid`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
In one of the DAO class, the SQL query goes like this.(The SQL query is against a derived class)
Query query = session.createQuery("select q.qid, q.qtype from Questions q where q.qlang=:lang ORDER BY q.addedtime ASC");
query.setParameter("lang", lang);
query.setFirstResult(startingRow).setMaxResults(10);
result = (List<Questions>) query.list();
Error happens in the above line result = (List<Questions>) query.list();
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown
column 'qid' in 'field list'
Questions
1) Why am I getting com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'addedtime' in 'field list' and how to fix it?
Please help. Struck on this for 3 days.
PS: I'm using Hibernate version 4.3.5.Final
PS: This is the query generated
select questions0_.qid as col_0_0_, questions0_.qtype as col_1_0_ from ( select qid, addedtime, qlang, qtype, null as option_1, null as option_2 as class_ from MCQ union select qid, addedtime, qlang, qtype, null as option_!, null as option_2 as class_ from TwoMarkQ) questions0_ where questions0_.qlang=? order by questions0_.addedtime ASC limit ?
Since the Query query = session.createQuery("select q.qid, q.qtype from Questions q where q.qlang=:lang ORDER BY q.addedtime ASC"); is on base class, it looks like it is making union with all the sub classes and sub classes doesn't have addedtime column. I'm just guessing.
then the error is due to the fact that in the table where hibernate is mapping there is no column, ie the field that usually defoult takes the name of the property.
The first question I want to ask you is the table is already present on the DB ?? why do you hibenate before doing anything you need to know with who you are mapping and in normal Hibernate use the classes let themselves be mapped to him.
So for when I can find out about hibernate to solve the problem you are redeeming you should map a single class with the one you have on the DB and then later map the "class of support" to your actual model.
Type a solution that is used when taking data from a server and the data does not reflect your current model.
I hope I have responded in line with your question and have been of help.
PS: Try to check if your db hibernate has not created other relational tabbells.
I try to post a bit of my code in hibernate version 4.x.x, why instead of writing the query do not try to use the Creteria and the Restrictions? the creteria gives you a list that you can decide if you want to return it already ordered, for example in my banal code of a person's search I had a generic DAO that implemet the query through the creteria.
//DAO Generic
public List<T> findByCriteria(Criterion... criterion) throws DAOException {
try {
Criteria crit = getSession().createCriteria(getPersistentClass());
for (Criterion c : criterion) {
crit.add(c);
}
return crit.list();
} catch (HibernateException ex) {
logger.error(ex.getLocalizedMessage());
throw new DAOException(ex);
}
}
//DAO person
public List<Persona> findByCognome(String cognome) throws DAOException {
try {
Criteria criteria = getSession().createCriteria(Persona.class);
criteria.add(Restrictions.eq("cognome", cognome).ignoreCase());
criteria.addOrder(Order.asc("eta"));
return criteria.list();
} catch (DAOException ex) {
throw new DAOException(ex.getLocalizedMessage());
}
}
Change the inheritance strategy to JOINED.
#Inheritance(strategy = InheritanceType.JOINED).
You might need to add a column for question type in question table which is discriminator column for the type of question which you need to put as annotation on Questions class.
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "QUESTION_TYPE")
public class Questions{
.
.
}
And, in the child classes, you can give the discriminator value using #DiscriminatorValue annotation.
#DiscriminatorValue("MCQ")
public class MCQ extends Questions implements Serializable{
.
}
and
#DiscriminatorValue("TwoMarkQ")
public class TwoMarkQ extends Questions implements Serializable{
.
}
and remove #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) from child classes.
Ok, This is how I resolved this issue.
The root cause is extending the base class Question.
I created a new class called Ques, which is implemented by Question.java, MCQ.java and TwoMarkQ.java
As per Hibernate documentation
We are provided with 3 options.
InheritanceType.TABLE_PER_CLASS or InheritanceType.SINGLE_TABLE or InheritanceType.JOINED
JOINED: is definitely not what I wanted. so it is a ruled out option.
SINGLE_TABLE:
The single table inheritance strategy maps all subclasses to only one database table.So this also leads to union of sub classes extending base class.
I still have following
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#Polymorphism(type=PolymorphismType.EXPLICIT)
on Question.java, MCQ.java and TwoMark.java
TABLE_PER_CLASS
"When using polymorphic queries, a UNION is required to fetch the base class table along with all subclass tables as well." - So this is also a ruled out option.
Moreover I removed the foreign key references between the tables.
I'm making a RESTful API web service with Spring Boot and MySQL. At the top level, I have an entity sites. On a second level an entity floors. Each site may have several floors.
Here is class Floors:
#Entity
public class Floors {
#Id
private String id;
private String floor;
#ManyToOne
private Sites site;
public Sites getSite() {
return site;
}
public void setSite(Sites site) {
this.site = site;
}
public Floors(){}
public Floors(String id, String floor, String siteId) {
super();
this.id = id;
this.floor = floor;
this.site = new Sites(siteId, "", "");
}
//getters and setters
Here is FloorsController:
#RestController
public class FloorsController {
#Autowired
private FloorsService floorsService;
#RequestMapping("/api/sites/{id}/floors")
public List<Floors> getAllFloors(#PathVariable String id){
return floorsService.getAllFloors(id);
}
#RequestMapping(method = RequestMethod.POST, value = "/api/sites/{siteId}/floors")
public void addFloor(#RequestBody Floors floor, #PathVariable String siteId){
floor.setSite(new Sites(siteId, "",""));
floorsService.addFloor(floor);
}
}
Here is SitesService:
#Service
public class SitesService {
#Autowired
private SitesRepository sitesRepository;
public List<Sites> getAllSites(){
List<Sites> sites = new ArrayList<>();
sitesRepository.findAll()
.forEach(sites::add);
return sites;
}
public void addSite(Sites site){
sitesRepository.save(site);
}
}
And here is my FloorsRepository:
public interface FloorsRepository extends CrudRepository<Floors, Integer> {
public List<Floors> getFloorsBySiteId(String siteId);
}
Then in MySQL Workbench, I created two tables:
CREATE TABLE `employeelocator1`.`sites` (
`id` VARCHAR(45) NOT NULL,
`site` VARCHAR(45) NULL,
PRIMARY KEY (`id`));
CREATE TABLE `employeelocator1`.`floors` (
`id` VARCHAR(45) NOT NULL,
`floor` VARCHAR(45) NULL,
`siteId` VARCHAR(45) NULL,
PRIMARY KEY (`id`),
INDEX `FK_siteId_idx` (`siteId` ASC),
CONSTRAINT `FK_siteId`
FOREIGN KEY (`siteId`)
REFERENCES `employeelocator1`.`sites` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION);
When I use /api/sites to get the list of all sites it works well. But when I try to get the list of all floors in site #1 using /api/sites/1/floors, I get an error:
o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1054, SQLState: 42S22
o.h.engine.jdbc.spi.SqlExceptionHelper : Unknown column 'floors0_.site_id' in 'field list'
Is it due to something wrong with my database and tables? Or something wrong with the code?
Found the answer to my question. FK in floors table should have been named site_id and not siteId
In the Sites Entity, you also can define like this to solve your problem:
#Id
#Column(name = "id", nullable = false)
private String site_id;