Using MyBatis, how can one clear a table and insert new values in one query?
Normal SQL I am thinking something along the lines of:
START TRANSACTION;
DELETE...
INSERT...;
COMMIT;
Is there any way of translating this over to MyBatis within the XML format?
I have thought of making a simple <sql> element with the delete and insert commands, but then how would I call this from within the XML? Surely each action must be called from their appropriate tag (<insert>, <delete>)
?
For example...
Say I am wanting to clear a table called Students of all students and populate the same table with new students. I would first clear the table by either DELETE FROM Students or TRUNCATE TABLE Students, and then I would want to repopulate the table with data etc...
INSERT INTO Students
(name,age,class)
VALUES
(John,12,A),
(Jim,12,A),
(Jess,13,C);
StudentServiceImpl.java
public class StudentServiceImpl implements StudentService {
Student get(Integer id) { ... };
Student getByName(String name) { ... };
Student update(Student student) { ... };
Student create(Student student) { ... };
void delete(Integer id) { ... };
List<Student> list(int offset, int limit, String name) { ... };
}
StudentMapper.java
public interface StudentMapper {
Student getStudent (Integer id);
Student getStudent ByName(#Param("name") String name);
void update(Student student);
void create(Student student);
void delete(Integer id);
List<Student> list(RowBounds rowBounds);
List<Student> listByName(#Param("name") String name,RowBounds rowBounds);
}
In theory this is possible using
<insert id="badWayOfInserting">
${SQL}
</insert>
and passing a string variable called 'SQL' to badWayOfInserting. Altough not recommendable.
The best option is to use transactions of CDI. You would have a method with #Transactional annotation. More or less like this:
#Transactional
public void deleteAndInsert(List<Item> listOfItems){
db.deleteDB();
db.insertItems(listOfItems);
}
CDI transactions are the same as DB ones. It will only commit if everything goes OK.
Related
I have two tables:
Users (id, name)
Events (id, name, date, user_created_id, user_updated_id (etc...)
How can I make a relationship between the two ids of the Events table with the user id?
user_created_id -> users.id
user_updated_id -> users.id
UPDATE
$table->integer('user_created_id')->unsigned();
$table->integer('user_updated_id')->nullable();
$table->foreign('user_created_id')->references('id')->on('users');
$table->foreign('user_updated_id')->references('id')->on('users');
If your question is how to make a foreign key for those two columns, then here:
$table->unsignedInteger('user_created_id');
$table->unsignedInteger('user_updated_id');
$table->foreign('user_created_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('user_updated_id')->references('id')->on('users')->onDelete('cascade');
If your id column on the users table is bigIncrements then replace the unsignedInteger with unsignedBigInteger
In the Event model, you can define two relationships:
public function user_created(){
return $this->belongsTo('App\User', 'user_created_id');
}
public function user_updated(){
return $this->belongsTo('App\User', 'user_updated_id');
}
In your User model, you can also define the reverse relationships:
public function created_events(){
return $this->hasMany('App\Event', 'user_created_id');
}
public function updated_events(){
return $this->hasMany('App\Event', 'user_updated_id');
}
With these relationshops, you can call $event->user_created to retrieve the App\User linked to the user_created_id (same for the updated version).
With a App\User instance, you can call $user->created_events to get a collection of App\Event (again, it's the same with $user->updated_events).
I'm having an issue with inserting new rows into my MySQL database. I'm using Spring Boot with Spring Boot Data JPA.
Since MySQL doesn't support sequences, I decided to try and make my own sequence generator table. This is basically what I've done.
I created a sequences table that uses an auto increment field (used as my id's for my tables).
Created a function, sequences_nextvalue() which inserts into the sequences table and returns the new auto incremented id.
I then created triggers on each table that get triggered before insertion and replaces the id field with the result of calling sequences_nextvalue().
So this is working fine when inserting new rows. I'm getting unique ids across all tables. The issue I'm having is with my JPA entities.
#Entity
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractBaseClass {
#Id
private Integer id = -1;
...
}
#Entity
public class ConcreteClass1 extends AbstractBaseClass {
...
}
#Entity
public class ConcreteClass2 extends AbstractBaseClass {
...
}
I want to be able to query from the abstract base class so I've placed my #Id column in that class and used #Entity with InheritanceType.TABLE_PER_CLASS. I've also initialized the id to -1 since an id is required to call save() from my spring crud repository.
After calling the save() function of my Spring data CrudRepository, the -1 for id properly gets replaced by the MySQL trigger but the resulting entity returned by save() doesn't return with the new id but instead retains the -1. After looking at the SQL logs, a select statement is not being called after insertion to get the new id but instead the original entity is being returned.
Is it possible to force Hibnerate to re-select the entity after insertion to get the new id when you're not using #GeneratedValue?
Any help is greatly appreciated.
Just wanted to provide an update on this question. Here is my solution.
Instead of creating MySQL TRIGGER's to replace the id on INSERT, I created a Hibernate IdentifierGenerator which executes a CallableStatement to get and return a new id.
My abstract base class now looks like this.
#Entity
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractBaseClass {
#Id
#GenericGenerator(name="MyIdGenerator", strategy="com.sample.model.CustomIdGenerator" )
#GeneratedValue(generator="MyIdGenerator" )
private Integer id;
...
}
and my generator looks like this.
public class CustomIdGenerator implements IdentifierGenerator {
private Logger log = LoggerFactory.getLogger(CustomIdGenerator.class);
private static final String QUERY = "{? = call sequence_nextvalue()}";
#Override
public Serializable generate(SessionImplementor session, Object object) throws HibernateException {
Integer id = null;
try {
Connection connection = session.connection();
CallableStatement statement = connection.prepareCall(QUERY);
statement.registerOutParameter(1, java.sql.Types.INTEGER);
statement.execute();
id = statement.getInt(1);
} catch(SQLException e) {
log.error("Error getting id", e);
throw new HibernateException(e);
}
return id;
}
}
And just for reference
The sequences table.
CREATE TABLE sequences (
id INT AUTO_INCREMENT PRIMARY KEY,
thread_id INT NOT NULL,
created DATETIME DEFAULT CURRENT_TIMESTAMP
) ^;
The sequence_nextvalue function
CREATE FUNCTION sequence_nextvalue()
RETURNS INTEGER
NOT DETERMINISTIC
MODIFIES SQL DATA
BEGIN
DECLARE nextvalue INTEGER;
INSERT INTO sequences (thread_id) VALUE (CONNECTION_ID());
SELECT id FROM sequence_values ORDER BY created DESC LIMIT 1 INTO nextvalue;
RETURN nextvalue;
END ^;
I have a GiftCards table in my DBML that has a related property called Audit. The Audits are stored in a separate table. Each Audit has a related Person associated to it. There is also a Persons table. The relationships are set up and are valid in my DBML.
The problem is that when I instantiate a new Gift Card I also create a new related Audit in the OnCreated() method. But at the same time, I also create a related Person when I instantiate a new Audit. The Person is the current user. Actually the Audit's OnCreated method checks if the user already exists.
The problem is that when I instantiate a new gift Card, it also creates an associated Audit, which is fine, and the Audit creates an associated Person. But the Person already exists in the database. When I look at the data context's GetChangeSet(), it shows 3 inserts. The Persion should not show as an insert because he already exists in the database.
Here is how I implemented this. It is an MVC application where the Controller receives a gift card:
[HttpPost]
public ActionResult Save(GiftCardViewModel giftCard)
{
if (ModelState.IsValid)
{
GiftCard gc = GiftCardViewModel.Build(giftCard);
repository.InsertOrUpdate(gc);
repository.Save();
return View("Consult", new GiftCardViewModel(repository.Find(gc.GiftCardID)));
}
else
SetupContext();
return View("_Form", giftCard);
}
The Gift Card has:
partial class GiftCard
{
partial void OnCreated()
{
// Set up default audit.
this.Audit = new Audit();
}
}
The Audit class has:
partial void OnCreated()
{
// Setup timestamp
this.Timestamp = DateTime.Now;
this.Person = Person.GetPerson(Membership.GetUser().UserName);
}
And finally, my Person class has:
public static Person GetPerson(String username)
{
using (GiftCardDBDataContext database = new GiftCardDBDataContext())
{
// Try to get the person from database
Person person = database.Persons.SingleOrDefault(personData => SqlMethods.Like(personData.Username, username));
if (person == null)
{
person = new Person()
{
Username = username,
FullName = "Full name TBD"
};
database.Persons.InsertOnSubmit(person);
database.SubmitChanges();
}
// Return person data
return person;
}
}
When I create a new gift card, I always get an error saying that it's attempting to insert a duplicate person in the Persons table. I don't understand because my static class specifically checks if the Person already exists, if yes, I return the Person and I don't create a new one. Yet, the GetChangeSet() shows three inserts including the Person, which is wrong.
What am I doing wrong here?
I believe your issue here is that you're using multiple contexts. You have one being created by your repository, and another is created in the static method on your Person object. You also aren't making any effort to attach the Person created/retrieved from the other context to the context of your Audit class.
You should look at a single unit of work, a single DataContext class, and perform all your work in that.
I am new to Entity Framework and hence this question may seem a little noobish.
I will try to explain my scenario with he Department-Employee example I have two tables "Department" and "Employee". Department has an identity column DeptID. I am trying to create a new Department and add newly created Employees to it all in one go. Below is my code:
using (MyDB context = new MyDB())
{
Department dept = new Department();
dept.Name = "My Department";
Employee emp = new Employee();
emp.Name = "Emp Name";
emp.Department = dept; //Tried dept.Employees.Add(emp) also, same result
context.AddObject("Department", dept);
context.SaveChanges()
}
But for some reason, the record doesn't get inserted. It throws an error in the second insert query.
Below are the queries:
INSERT INTO Department
(Name)
VALUES ('Dept1' /* #gp1 */);
SELECT ID
FROM Department
WHERE row_count() > 0
AND `ID` = last_insert_id()
--------------------------
INSERT INTO Employee
(DeptID,
Name)
VALUES (19,
'Name'); /* #gp1 */
SELECT id
FROM Employee
WHERE row_count() > 0
AND `id` = last_insert_id()
The error it throws is at line 4 of second query. So I am guessing something is wrong with the Identity thing. I am using MySQL.
Can anyone please explain what could be wrong?
EDIT: I have modified the SQL to suit this example. I can't give my real table details.
What is the structure of your classes? I'd assume there is something slightly wrong and EF isn't correctly building the model.
Also I had some problems with EF4.1 until I manually defined the key. The part of EF that 'assumes' which variable is your key doesn't seem to work on some complex objects like objects that are derived from a base class and also fails in other cases.
Here is what I would expect your code to look like:
public class Department
{
[Key]
public Int64 DepartmentId { get; set;}
public String Name { get; set;}
}
public class Employee
{
[Key]
public Int64 EmployeeId { get; set;}
public String Name { get; set;}
//Adding virtual here allows lazy loading of department
public virtual Department Department {get; set;}
}
public class MyDatabase : DbContext
{
DbSet<Department> Departments;
DbSet<Employee> Employees;
}
I have an entire project at work that relies on Entity framework correctly mapping the above into a Many to One relationship and I've had no issues using code just as shown.
'Name' /* #gp1 */
is missing a close parenthesis. i've added it here.
'Name' ) /* #gp1 */
I have an JPA entity like this:
#Entity
#Table(name = "category")
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#Column(name = "name")
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "category")
private Collection<ItemCategory> itemCategoryCollection;
//...
}
Use Mysql as the underlying database. "name" is designed as a unique key. Use Hibernate as JPA provider.
The problem with using merge method is that because pk is generated by db, so if the record already exist (the name is already there) then Hibernate will trying inserting it to db and I will get an unique key constrain violation exception and not doing the update . Does any one have a good practice to handle that? Thank you!
P.S: my workaround is like this:
public void save(Category entity) {
Category existingEntity = this.find(entity.getName());
if (existingEntity == null) {
em.persist(entity);
//code to commit ...
} else {
entity.setId(existingEntity.getId());
em.merge(entity);
//code to commit ...
}
}
public Category find(String categoryName) {
try {
return (Category) getEm().createNamedQuery("Category.findByName").
setParameter("name", categoryName).getSingleResult();
} catch (NoResultException e) {
return null;
}
}
How to use em.merge() to insert OR update for jpa entities if primary key is generated by database?
Whether you're using generated identifiers or not is IMO irrelevant. The problem here is that you want to implement an "upsert" on some unique key other than the PK and JPA doesn't really provide support for that (merge relies on database identity).
So you have AFAIK 2 options.
Either perform an INSERT first and implement some retry mechanism in case of failure because of a unique constraint violation and then find and update the existing record (using a new entity manager).
Or, perform a SELECT first and then insert or update depending on the outcome of the SELECT (this is what you did). This works but is not 100% guaranteed as you can have a race condition between two concurrent threads (they might not find a record for a given categoryName and try to insert in parallel; the slowest thread will fail). If this is unlikely, it might be an acceptable solution.
Update: There might be a 3rd bonus option if you don't mind using a MySQL proprietary feature, see 12.2.5.3. INSERT ... ON DUPLICATE KEY UPDATE Syntax. Never tested with JPA though.
I haven't seen this mentioned before so I just would like to add a possible solution that avoids making multiple queries. Versioning.
Normally used as a simple way to check whether a record being updated has gone stale in optimistic locking scenario's, columns annotated with #Version can also be used to check whether a record is persistent (present in the db) or not.
This all may sound complicated, but it really isn't. What it boils down to is an extra column on the record whose value changes on every update. We define an extra column version in our database like this:
CREATE TABLE example
(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
version INT, -- <== It really is that simple!
value VARCHAR(255)
);
And mark the corresponding field in our Java class with #Version like this:
#Entity
public class Example {
#Id
#GeneratedValue
private Integer id;
#Version // <-- that's the trick!
private Integer version;
#Column(length=255)
private String value;
}
The #Version annotation will make JPA use this column with optimistic locking by including it as a condition in any update statements, like this:
UPDATE example
SET value = 'Hello, World!'
WHERE id = 23
AND version = 2 -- <-- if version has changed, update won't happen
(JPA does this automatically, no need to write it yourself)
Then afterwards it checks whether one record was updated (as expected) or not (in which case the object was stale).
We must make sure nobody can set the version field or it would mess up optimistic locking, but we can make a getter on version if we want. We can also use the version field in a method isPersistent that will check whether the record is in the DB already or not without ever making a query:
#Entity
public class Example {
// ...
/** Indicates whether this entity is present in the database. */
public boolean isPersistent() {
return version != null;
}
}
Finally, we can use this method in our insertOrUpdate method:
public insertOrUpdate(Example example) {
if (example.isPersistent()) {
// record is already present in the db
// update it here
}
else {
// record is not present in the db
// insert it here
}
}