在〈簡化 JDBC 與 Mail〉之後,看看其中的 AccountDAO
與 MessageDAO
實作,幾乎就只剩下 SQL 語句了,這不禁人想到〈簡介 Spring Data JDBC〉,也只是下下 SQL 就了事了。
那就在你的 build.gradle 加上 Spring Data JDBC 相依吧!
implementation('org.springframework.boot:spring-boot-starter-data-jdbc')
在原本的 AccountDAO
實作中,createAccount
方法下了兩條 SQL 語句,分別在兩個表格新增資料,這是因為一開始,本預計使用者可能會擁有多個角色,為了一開始不要讓事情變得複雜,將新增使用者與角色都隱藏在 createAccount
之中。
其實這應該是兩個任務,基本上應該另外定義個 AccountRoleDAO
來處理使用者角色對應的問題,不過,gossip 應用程式實際上只用到一個 ROLE_MEMBER
角色,乾脆就把角色寫在與帳號表格好了,如此,AccountDAO
就可以只寫為:
package cc.openhome.model;
import java.util.Optional;
import org.springframework.data.jdbc.repository.query.Modifying;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
public interface AccountDAO extends CrudRepository<Account, Integer> {
@Query("SELECT * FROM account WHERE name = :name")
Optional<Account> accountByUsername(@Param("name") String name);
@Query("SELECT * FROM account WHERE email = :email")
Optional<Account> accountByEmail(@Param("email") String email);
@Modifying
@Query("UPDATE account SET enabled = 1 WHERE name = :name")
void activateAccount(@Param("name") String name);
@Modifying
@Query("UPDATE account SET password = :password WHERE name = :name")
void updatePassword(@Param("name") String name, @Param("password") String password);
}
MessageDAO
就單純多了:
package cc.openhome.model;
import java.util.List;
import org.springframework.data.jdbc.repository.query.Modifying;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
public interface MessageDAO extends CrudRepository<Message, Integer> {
@Query("SELECT * FROM message m WHERE m.username = :username")
List<Message> messagesBy(@Param("username") String username);
@Modifying
@Query("DELETE FROM message WHERE username = :username AND millis = :millis")
void deleteMessageBy(@Param("username") String username, @Param("millis") String millis);
@Query("SELECT * FROM message ORDER BY millis DESC LIMIT :n")
List<Message> newestMessages(@Param("n") int n);
}
原本 AccountDAO
與 MessageDAO
各定義了儲存帳號與訊息的方法,現在因為繼承了 CrudRepository
,而它本身有個 save
方法,因此就可以刪掉 AccountDAO
與 MessageDAO
中各自儲存帳號與訊息的方法。
表格本身則重新調整如下:
CREATE TABLE account (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(15) NOT NULL,
email VARCHAR(128) NOT NULL,
password VARCHAR(64) NOT NULL,
enabled TINYINT NOT NULL,
role VARCHAR(15) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE message (
id INT NOT NULL AUTO_INCREMENT,
username VARCHAR(15) NOT NULL,
millis BIGINT NOT NULL,
blabla VARCHAR(512) NOT NULL,
PRIMARY KEY (id)
);
在 Account
部份,標示 @Id
並加上對應的欄位:
package cc.openhome.model;
import org.springframework.data.annotation.Id;
public class Account {
@Id
private Integer id;
private String name;
private String email;
private String password;
private Integer enabled;
private String role;
public Account() {
}
public Account(String name, String email, String password, Integer enabled, String role) {
this.name = name;
this.email = email;
this.password = password;
this.enabled = enabled;
this.role = role;
}
public Account(String name, String email, String password) {
this(name, email, password, 0, "ROLE_MEMBER");
}
...略
}
Message
也是:
package cc.openhome.model;
import java.time.*;
import org.springframework.data.annotation.Id;
public class Message {
@Id
private Integer id;
private String username;
private Long millis;
private String blabla;
...略
}
因為調整了表格,在 Spring Security 的 JDBC 驗證部份,SQL 要做點修改:
auth.jdbcAuthentication()
.passwordEncoder(passwordEncoder)
.dataSource(dataSource)
.usersByUsernameQuery("select name, password, enabled from account where name=?")
.authoritiesByUsernameQuery("select name, role from account where name=?");
因為調整了 AccountDAO
、MessageDAO
等協定,UserService
勢必也要做點相應的變化,主要就是與新增操作有關的方法:
package cc.openhome.model;
...歄
@Service
public class UserService {
...略
private Account createUser(String username, String email, String password) {
Account acct = new Account(username, email, passwordEncoder.encode(password), 0, "ROLE_MEMBER");
accountDAO.save(acct);
return acct;
}
...略
public void addMessage(String username, String blabla) {
messageDAO.save(new Message(
username, Instant.now().toEpochMilli(), blabla));
}
...略
}
你可以在 gossip 找到以上的範例專案。