Google Code Prettify

顯示具有 spring 標籤的文章。 顯示所有文章
顯示具有 spring 標籤的文章。 顯示所有文章

2017年7月27日 星期四

變更 property 檔裡的設定值

Java 的程式設定習慣放在 property 檔,這些檔會放置在 classpath 的某處,大部份時候程式都只需要讀取檔案裡的資料,要變更資料是人工變更,但是,萬一程式想變更怎麼辦? 底下式可以辦到 …

紅色部份比較常見,就是讀入 application.properties 裡的資料到 prop 裡,綠色部份是變更想變更的值後在存回去。
Properties prop = new Properties();

URL url = this.getClass().getClassLoader().getResource("application.properties");
URI uri = url.toURI();
File file = new File(uri);
   
InputStream is = new FileInputStream(file);
prop.load(is);
is.close();
   
FileOutputStream os = new FileOutputStream(file);
prop.setProperty("myKey", myValue);
prop.store(os, null);
os.close();
上面這樣就搞定了嗎? 通常應該沒有這麼好過 … Orz...
因為程式中如果有用到這個 property 檔的相關變數應該也要被更新,在 spring framework 中,載入 property 檔,是在 @Configuration 所在的類別再增加如下註釋。
@PropertySource("classpath:application.properties")
在需要使用裡面的設定的類別裡如下載入。
@Value("${myKey}")
private String myValue;
因為我們已經將 property 檔委由 spring framework 管理,要重新載入,就要從 spring 的環境著手。
@Inject
private StandardEnvironment environment;
...
MutablePropertySources propertySources = environment.getPropertySources();
PropertySource resourcePropertySource = propertySources.get("class path resource [application.properties]");
        
URL url = this.getClass().getClassLoader().getResource("application.properties");
URI uri = url.toURI();
File file = new File(uri);
  
InputStream is = new FileInputStream(file);
Properties prop = new Properties();
prop.load(is);
is.close();
        
propertySources.replace("class path resource [application.properties]", new PropertiesPropertySource("class path resource [application.properties]", prop));




2017年7月23日 星期日

Spring Application Event

spring framework 的 bean 一般熟知的就是 IoC 的依賴注入 (dependency injection),這裡介紹一個較少用到的功能,bean 與 bean 之前傳遞事件! 既然是談到事件,最好就先了解一下 Observer Pattern,spring framework 實作了這個 pattern,讓程式中有兩個 bean,當 bean A 狀態有變化時,可以通知 bean B,說明如下:
  • Event
 1 @Data
 2 public class MyBeanEvent extends ApplicationEvent {
 3     private String msg;
 4 
 5     public MyBeanEvent(Object source) {
 6         super(source);
 7         
 8         this.msg = (String) source;
 9     }
10 }
首先定義一個事件類別,這個類別需繼承 ApplicationEvent 類別,我們自己定義的事件類別需有建構子 (constructor),即程式中 5 ~ 9 行,我們將傳遞過來的物件存入變數 msg 裡。
  • Publisher
1 @Component
2 public class MyPublisher {
3     @Inject
4     private ApplicationContext context;
5     
6     public void publish(String msg) {
7         context.publishEvent(new MyBeanEvent(msg));
8     }
9 }
發佈訊息的類別如上,使用 spring 的 ApplicationContext 的 publishEvent method 即可發佈出訊息物件,所有的傾聽者 bean 就會收到訊息物件。
  • Listener
1 @Component
2 public class MyListener implements ApplicationListener<MyBeanEvent> {
3 
4     @Override
5     public void onApplicationEvent(MyBeanEvent event) {
6         System.out.println("listen: " + event.getMsg());
7     }
8 }
傾聽類別要實作 ApplicationListener 介面,並指出要傾聽的事件類別,如上面的第 2 行,這個介面會要求傾聽類別實作一個 onApplicationEvent method,當 publisher 發出事件時,這個 method 即會收到該事件,這裡很簡單的將收到的事件內容印出來 (line 6)。
  • Config
1 @Configuration
2 @ComponentScan("idv.steven.info.tse")
3 public class MyEventConfig {
4 
5 }
要測試 publisher 送出的訊息是否會被 listener 收到,當然要先讓 spring 將它們載入,因為 publisher 和 listener 兩個 bean 是被在 idv.steven.info.tse package 下,所以第 2 行就去掃描該 package。
  • Main
1 public class MyTestMain {
2 
3     public static void main(String[] args) {
4         AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyEventConfig.class);
5         MyPublisher publisher = context.getBean(MyPublisher.class);
6         publisher.publish("Hello World");
7         context.close();
8     }
9 }
主程式很簡單,使用 spring 的 AnnotatioinConfigApplicationContext 載入 MyEventConfig,MyEventConfig 會去掃描指定的 package 將標有 @Component 相關的 bean 載入,載入後就如第 5 行,主程式取得 publisher 物件,然後在第 6 行發佈出 "Hello World" 的訊息,執行的結果就可以看到 console 印出如下訊息:
listen: Hello World



2017年1月21日 星期六

JPA + Spring: 資料庫連線設定 (JNDI@Tomcat)

Tomcat 應該是 Java 世界使用最廣的 AP server? 即使正式營運主機上安裝的是商業的 AP server (IBM WebSphere、BEA WebLogic ...),程式員在開發程式時,也多半是在本機安裝 Tomcat 用來測試,這裡整理一下兩種建立資料庫連線的方式,一個是使用 Tomcat JNDI 的方式,另一個是使用 apache commons dbcp2,用這兩個方式都可以有 connection pool,在大部份的情況下,在正式營運環境會採用 JNDI,在本機開發則可能採用 dbcp2。
  • JDBC driver
將資料庫的 JDBC driver 放到 $Tomcat$/lib 目錄下,我使用的資料庫是 oracle,所以我將 ojdbc7.jar 放到 $Tomcat$/lib 目錄下。
  • 設定 server.xml


在 eclipse 中加入 Server 後,可以選擇要將那些專案加入 server 中,這裡選擇一個命名為 Party 的專案,按【Finish】後,在 server.xml 中就會產生如下內容:
<Context docBase="Party" path="/Party" reloadable="true" source="org.eclipse.jst.jee.server:Party" />
接著在裡面加入我們要的 JNDI 設定,如下:
<Context docBase="Party" path="/Party" reloadable="true" source="org.eclipse.jst.jee.server:Party">
    <Resource auth="Container" driverClassName="oracle.jdbc.OracleDriver" maxIdle="10" maxTotal="20" maxWaitMillis="-1" name="jdbc/DemoDB" password="passw0rd" type="javax.sql.DataSource" url="jdbc:oracle:thin:@192.168.0.102:1521:DemoDB" username="steven"/>
</Context>
這樣設定完,啟動 Tomcat,Tomcat 就會自動建立起資料庫的連線,接下要看看程式要怎麼使用。
  • web.xml
<resource-ref>
     <description>oracleDB</description>
     <res-ref-name>jdbc/DemoDB</res-ref-name>
     <res-type>javax.sql.DataSource</res-type>
     <res-auth>Container</res-auth>
</resource-ref>
在專案的 web.xml 中,加入如上內容,裡面的 res-ref-name 指出了要使用的 JNDI 資源名稱,這裡當然就是上一步驟設定好的資料庫連線。
  • WebInitializer
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { RootConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { MvcConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}
在 spring MVC 的專案中,會以一個繼承 AbstractAnnotationConfigDispatcherServletInitializer 的類別為起始入口,就像一般在寫 application 會以 main 為起始入口一樣,這裡有兩個重要設定,在 getRootConfigClasses 中指出程式啟動後,要優先執行的設定類別,在 getServletConfigClasses 中則指出處理 servlet 的類別。雖然上面的設定都只有一個類別,實際上那是陣列,也就是說可以有多個。這一篇的重點不在 spring MVC,所以後續只會說明 RootConfig。
  • 在啟始類別中建立資料庫連線
 1 @Configuration
 2 @ComponentScan(
 3         basePackages = {"idv.steven.invitation"},
 4         includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Configuration.class),
 5         excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)
 6     )
 7 @Log4j
 8 public class RootConfig {
 9     
10     @Bean
11     @Profile("PROD")
12     public JndiObjectFactoryBean jndiObjectFactoryBean() {
13         JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean();
14         jndiObjectFB.setJndiName("jdbc/DemoDB");
15         jndiObjectFB.setResourceRef(true);
16         jndiObjectFB.setProxyInterface(javax.sql.DataSource.class);
17         
18         return jndiObjectFB;
19     }
20     
21     @Bean
22     @Profile("TEST")
23     public DataSource dataSource() {
24         BasicDataSource ds = new BasicDataSource();
25         ds.setDriverClassName("oracle.jdbc.OracleDriver");
26         ds.setUrl("jdbc:oracle:thin:@192.168.0.102:1521:DemoDB");
27         ds.setUsername("steven");
28         ds.setPassword("passw0rd");
29         ds.setInitialSize(5);
30         
31         return ds;
32     }
33 }
  1. 如文章一開頭說的,在正式營運環境和開發環境有可能使用不同的資料庫連線方式,這裡就是這樣,建立了兩個不同的連線方式,一個是使用 JndiObjectFactoryBean,另一個使用 DataSource。
  2. 第 14 行設定要使用那個 JNDI 資源,當第 15 行的 setResourceRef 設定為 true 時,就寫成如上面的方式,如果設定為 false,就得寫成 java:comp/env/jdbc/DemoDB。
  3. 第 11、22行指出這兩個 bean 會在什麼環境裡啟動,至於在什麼環境要怎麼判斷? 這是在 web.xml 中設定的,如下,裡面設定了 spring.profiles.active 的值為 PROD,這表示程式啟動時,只有 JndiObjectFactoryBean 會被建立,DataSource 不會被建立,反之亦然。
<context-param>
      <param-name>spring.profiles.active</param-name>
      <param-value>PROD</param-value>
</context-param>
  • JPA config
 1 @Configuration
 2 @EnableTransactionManagement
 3 @EnableJpaRepositories (
 4     basePackages = { "idv.steven.invitation.database.dao" },
 5     entityManagerFactoryRef = "entityManagerFactory",
 6     transactionManagerRef = "jpaTransactionManager"
 7 )
 8 public class JpaConfig {
 9     @Autowired(required=false)
10     private DataSource dataSource;
11     
12     @Autowired(required=false)
13     private JndiObjectFactoryBean jndiObjectFB;
14     
15     @Bean
16     public JpaVendorAdapter jpaVendorAdapter() {
17         HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
18         
19         adapter.setDatabase(Database.ORACLE);
20         adapter.setShowSql(true);
21         adapter.setGenerateDdl(false);
22         adapter.setDatabasePlatform("org.hibernate.dialect.Oracle10gDialect");
23         
24         return adapter;
25     }
26     
27     @Bean
28     public PlatformTransactionManager jpaTransactionManager() {
29         JpaTransactionManager tm = new JpaTransactionManager();
30         tm.setEntityManagerFactory(this.entityManagerFactory().getObject());
31         
32         return tm;
33     }
34         
35     @Bean
36     public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
37         LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
38         if (dataSource == null)
39             emf.setDataSource((DataSource) jndiObjectFB.getObject());
40         else
41             emf.setDataSource(dataSource);
42         emf.setPackagesToScan(new String[] { "idv.steven.invitation.database.entity" });
43         emf.setJpaVendorAdapter(this.jpaVendorAdapter());
44         
45         return emf;
46     }
47 }
這個類別在「JPA + Hibernate + Spring + JUnit (annotation)」中已有說明,這裡只針對粗體的部份補充說明,第 9、12 行一定要有 required=false 的設定,因為兩個只會有一個被建立,如果沒有設定,預設是 required=true,那麼一定會有一個因為在 RootConfig 中沒有建立,在這裡無法被注入。第 38~41 行在設定 DataSource 時,當然要判斷一下現在那個有建立就用那一個。到這裡就搞定了 ...




Forza Gesù -- 一個小女孩成長的故事】
義大利教會合唱團的這首歌在網路上被廣傳,但是最受歡迎的是影片中影片那個版本,是主唱 2010 年 4 歲第一次領唱時的影片,到了 2016 年 10 歲時小女孩長大了,失去了 4 歲時那份混然天成的童真,就沒辦法感動那麼多人了 … 還是來看一下 2010 年的版本吧 ~



2017年1月11日 星期三

JPA + Spring: 查詢結果為非實體 table (複合型別)


上圖兩個 table 是一對多的關係,要查詢出飯局的地點在那一個餐廳,及餐廳的相關資料,可以用之前介紹過的一對多方式 …
這篇要說明的是另一種方式,這個方式在三個或三個以上 table join 時,特別有用。假設我們要傳回的欄位如下:
 1 @Entity
 2 @Data
 3 public class AppointmentDetail {
 4     private String email; //電子郵件
 5     private String name; //飯局名稱
 6     private String restaurantName; //餐廳名稱
 7     private String restaurantAddr; //餐廳地址
 8     private String description; //飯局說明
 9     private int people; //參與人數
10     private int willPay; //消費額
11     private Date htime; //舉辦時間
12     
13     @Id
14     private Long sysId; //序號
15 }
如上的類別 AppointmentDetail 包含了兩個 table 的欄位,這個類別一樣要用 @Entity 標示,且也要用 @Id 標示可成為唯一的欄位,特別說明的是第 2 行的 @Data,這和 JPA 或 Spring 無關,這是 Lombok 提供的功能,用來簡化程式設計,在這邊加上 @Data 就可以為類別裡所有的欄位 (field) 加上 getter、setter method,這解決了程式設計人員老是在 POJO 中廢時寫 getter、setter 的困擾。

接著在 DAO 中增加一個 method 如下,寫法沒什麼特別,只是傳回結果擁有兩個 table 的欄位。
1     public AppointmentDetail find(Long sysId) {
2         Query query = manager.createNativeQuery(
3                 "  select a.email, a.name, r.name restaurantName, r.address restaurantAddr, a.description, a.people, a.willPay, a.htime, a.sysId "
4                 + "from appointment a inner join restaurant r on a.restaurant_sysid = r.sysId where a.sysId = :sysId "
5                 , AppointmentDetail.class);
6         query.setParameter("sysId", sysId);
7         return (AppointmentDetail) query.getSingleResult();
8     }
測試程式如下,當然也和之前寫過的使用 DAO 的方式沒什麼不一樣。
1     @Transactional
2     @Test
3     public void testFindDetail() {
4         AppointmentDetail detail = daoApp.find(12L);
5         assertNotNull(detail);
6         System.out.println(detail.toString());
7     }
總結來說,唯一的不同在於 @Entity 類別,在那個類別中,不需要使用 @Table 來指出是屬於資料庫裡的那個 table。
另外,在 spring data 中提供另一種更簡潔的寫法,只要寫一個介面,繼承 Repository 或它的子介面 (CrudRepositoryPagingAndSortingRepository、RevisionRepository),可以直接在 method 上寫 sql,如下:
public interface AppointmentDetailDAO extends Repository<AppointmentDetail, Long> {
    @Query(value="select a.email, a.name, r.name as restaurantName, r.address as restaurantAddr, a.description, a.people, a.willPay, a.htime, a.sysId from Appointment a inner join Restaurant r on a.restaurant_sysId = r.sysId where a.sysId = :sysId", nativeQuery=true)
    public AppointmentDetail findDetailBySysId(@Param("sysId") Long sysId);
}
這裡要注意的是,AppointmentDetail 是複合型別,只能用 native query (nativeQuery 預設值為 false),不能用 JPQL,否則會有無法轉換型別的錯誤訊息「org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.Object[]] to type …」。




【Lombok 的使用】
  1. 從官網上下載 lombok.jar,將它放在 eclipse.ini 所在的目錄裡。
  2. 在 eclipse.ini 中加入如下內容:
  3. -Xms2048m
    -Xmx4096m
    -javaagent:lombok.jar
  4. 重啟 eclipse。

2017年1月8日 星期日

spring data: CrudRepository 自動生成

spring 專案針對 ORM 獨立出一個命名為 spring data 的專案,使用 spring data 可以更簡化程式設計,回頭看一下「JPA + Hibernate + Spring + JUnit (annotation)」,現在要將它用 spring data 改寫 …
  • 修改 JpaConfig
在類別的開頭加上紅色部份,指出要使用的 entityManagerFactory 及 transactionManager,還有就是要載入那些 package 下符合條件的介面,也就是繼承了 spring data 為方便程式開發者提供的一些制式介面,用來簡化程式,這些介面包含有 CrudPository、PagingAndSortingRepository、JpaRepository。
 1 @Configuration
 2 @EnableTransactionManagement
 3 @EnableJpaRepositories (
 4     basePackages = "idv.steven.invitation.database.dao",
 5     entityManagerFactoryRef = "entityManagerFactory",
 6     transactionManagerRef = "jpaTransactionManager"
 7 )
 8 public class JpaConfig {
 9     @Bean
10     public DataSource dataSource() {
11         BasicDataSource ds = new BasicDataSource();
12         ds.setDriverClassName("oracle.jdbc.OracleDriver");
13         ds.setUrl("jdbc:oracle:thin:@192.168.0.102:1521:DemoDB");
14         ds.setUsername("steven");
15         ds.setPassword("P@ssw0rd");
16         ds.setInitialSize(5);
17         
18         return ds;
19     }
20     
21     @Bean
22     public JpaVendorAdapter jpaVendorAdapter() {
23         HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
24         
25         adapter.setDatabase(Database.ORACLE);
26         adapter.setShowSql(true);
27         adapter.setGenerateDdl(false);
28         adapter.setDatabasePlatform("org.hibernate.dialect.Oracle10gDialect");
29         
30         return adapter;
31     }
32     
33     @Bean
34     public PlatformTransactionManager jpaTransactionManager() {
35         JpaTransactionManager tm = new JpaTransactionManager();
36         tm.setEntityManagerFactory(this.entityManagerFactory().getObject());
37         
38         return tm;
39     }
40         
41     @Bean
42     public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
43         LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
44         emf.setDataSource(this.dataSource());
45         emf.setPackagesToScan(new String[] {"idv.steven.invitation.database.dao", "idv.steven.invitation.database.entity" });
46         emf.setJpaVendorAdapter(this.jpaVendorAdapter());
47         
48         return emf;
49     }
50 }
  • 刪除 UsersDAOImpl 類別,並修改 UsersDAO 介面。
1 public interface UsersDAO extends CrudRepository<Users, String> {
2     
3 }
讓我們開發的介面繼承 CrudRepository,spring data 就會提供一些制式的 method,並自動產生實作的程式碼。在 CrudRepository 後面的 Users 及 String,分別指出 Entity 及 primary key 的型別。CrudRepository 提供的制式 method 如下: (參考 Professional Java for Web Applications: Featuring Websockets, Spring Framework, JPA Hibernate, and Spring Security 一書,p.  638.)
count() returns a long representing the total number of unfiltered entities extending T.
delete(T) and delete(ID) delete the single, specified entity, whereas delete(Iterable<? extends T>) deletes multiple entities and deleteAll() deletes every entity of that type.
exists(ID) returns a boolean indicating whether the entity of this type with the given surrogate key exists.
findAll() returns all entities of type T, whereas findAll(Iterable<ID>) returns the entities of type T with the given surrogate keys. Both return Iterable<T>.
findOne(ID) retrieves a single entity of type T given its surrogate key.
save(S) saves the given entity (insert or update) of type S where S extends T, and returns S, the saved entity.
save(Iterable<S>) saves all the entities (again, S extends T) and returns the saved entities as a new Iterable<S>.
  • 測試
雖然 UsersDAO 介面裡沒有任何 method,但是我們可以直接調用 CrudRepository 提供的 method …
1 @Transactional
2 @Test
3 public void findTalking() {
4     Users users = dao.findOne("hi.steven@gmail.com");
5     List<Appointment> talking = users.getAppointments();
6     for(Appointment t:talking) {
7         System.out.println("結果:" + t.toString());
8     }
9 }




【題外話 - 在 build.gradle 中設定排除特定 jar 檔】
要管理 java 的 jar 檔不是一件簡單的事,所以陸續出現了 Maven、Gradle 等解決方案 (當然這兩個軟體不只在解決 jar 檔的相依性),我現在慣用的是 Gradle,很方便的它會幫我找到所有相依的 jar 檔。但是,也可能出現 jar 檔會同時有兩個,只是版本不一樣,因為 open source 本來就不可完全協調的很一致,一定會有使用不同版本 jar 的可能性存在,當系統很龐大時,引入的 open source 甚至 framework 很多時,很難找出那個 jar 檔是因為那個 framework 的相依 jar 檔而被引入,像是我最近遇到的就是 gradle 抓回來的 jar 檔,同時存在著 hibernate-jpa-2.0-api 及 hibernate-jpa-2.1-api,這樣程式當然會出問題,必須在 build.gradle 中將不需要的 jar 檔排除! 方法很簡單,在 build.gradle 檔裡加入如下設定:
configurations {
    runtime.exclude group: 'org.hibernate.javax.persistence', module: 'hibernate-jpa-2.0-api'
}
runtime.exclude 算是指令,指出要排除那個 jar 檔,重點在於後面的 group、module 值我們怎麼知道要填什麼? 以上面的設定來說,要排除的是 hibernate-jpa-2.0-api-1.0.1.Final.jar,那麼就先在 Google 裡搜尋「 hibernate jpa maven」找到 maven 的 repository,由裡面可以找到 gradle 要由 maven repository 取得 jar 檔所需要的設定,如下:
compile group: 'org.hibernate.javax.persistence', name: 'hibernate-jpa-2.0-api', version: '1.0.1.Final'
值有三個 group、name、version,將 group的值填入上面的 group,name 的值填入上面的 module,這樣就大功告成了!

2017年1月2日 星期一

JPA + Spring: 繼承 (@MappedSuperclass)


在資料庫的設計中,有時會遇到如上的情況,兩個 table 屬性相同,都是儲放可登入系統的人,所需要的欄位也有許多重複,但是因為"管理者"和"使用者"是由不同介面登入,進入系統後可使用的功能也非常不同,因此被分別放在兩個 table。當發生這種狀況時,透過 ORM,在程式中可以將相同的欄位提取出來成為 super class,先來看一下 Administrator 怎麼寫。
 1 @Entity
 2 @Table(
 3         name="ADMINISTRATOR",
 4         uniqueConstraints = {
 5             @UniqueConstraint(columnNames = "EMAIL")
 6         })
 7 public class Administrator extends AbstractUser implements Serializable {
 8     private static final long serialVersionUID = 4001989953339069879L;
 9     
10     private String department;
11     private String roleId;
12     private String status;
13     
14     public String getDepartment() {
15         return department;
16     }
17     public void setDepartment(String department) {
18         this.department = department;
19     }
20     
21     public String getRoleId() {
22         return roleId;
23     }
24     public void setRoleId(String roleId) {
25         this.roleId = roleId;
26     }
27     
28     public String getStatus() {
29         return status;
30     }
31     public void setStatus(String status) {
32         this.status = status;
33     }
34     
35     @Override
36     public String toString() {
37         return "Administrotor [department=" + department + ", roleId=" + roleId + ", status=" + status + ", toString()="
38                 + super.toString() + "]";
39     }
40 }
兩個 table 共有的欄位被提取出來放到 AbstractUser 類別,其餘的欄位寫法和一般的 Entity 並沒有不同,再來看一下 AbstractUser 的寫法。
 1 @MappedSuperclass
 2 public class AbstractUser {
 3     private String email;
 4     private String password;
 5     private String occupation;
 6     private Date ctime;
 7     private Date lastLogin;
 8     
 9     @Id
10     public String getEmail() {
11         return email;
12     }
13     public void setEmail(String email) {
14         this.email = email;
15     }
16     public String getPassword() {
17         return password;
18     }
19     public void setPassword(String password) {
20         this.password = password;
21     }
22     public String getOccupation() {
23         return occupation;
24     }
25     public void setOccupation(String occupation) {
26         this.occupation = occupation;
27     }
28     public Date getCtime() {
29         return ctime;
30     }
31     public void setCtime(Date ctime) {
32         this.ctime = ctime;
33     }
34     public Date getLastLogin() {
35         return lastLogin;
36     }
37     public void setLastLogin(Date lastLogin) {
38         this.lastLogin = lastLogin;
39     }
40     
41     @Override
42     public String toString() {
43         return "AbstractUser [email=" + email + ", password=" + password + ", occupation=" + occupation + ", ctime="
44                 + ctime + ", lastLogin=" + lastLogin + "]";
45     }
46 }
如上,被提取出來成為上層類別,要在 class 前加上 @MappedSuperclass,這個上層類別也和 entity 一樣寫法,甚至 primary key 欄位也可以放在這 (第 9 行)。




【日劇 - 戀仲】
福士蒼汰和本田翼主演的純愛日劇,從青梅竹馬到步入禮堂,雖然歷經波折,雖然到最後一集編劇仍試著要製造點懸疑,但是,這類的戲大致上94%會是圓滿的結局,所以那個懸疑的效果真的太多餘了 ...
劇中福士蒼汰主演的三浦葵不斷的出現這種狂奔的畫面,我只能說,年輕真好! 要個中年人這麼演的話肯定吃不消!
上圖右邊是劇中男主角的競爭對手蒼井翔太 (野村周平 飾),左邊是三位主要人物之外最重要的配角金澤公平 (太賀 飾),是個對朋友非常熱心的人,當然,我知道編劇和導演想要塑造的是正面的形象,但是,說實在的,他的熱心已經過頭了! 往往過度的介入別人的人生,這我就不太能欣賞了!
本田翼不用特別形容她的漂亮,是男人看了都知道! 她演過不少日劇、日影了,一直到這兩三年戲份才多了起來,在本劇中她的演技真的很生硬,連走路的樣子都很生硬 … Orz ... 純愛戲嘛~ 女主角長得好看比演的好重要,不然叫觀眾要怎麼投入劇情裡呢?!
這是第一集一開頭演高中生時期,她幫男主角三浦葵加油的畫面。她真的蠻漂亮的,再放了一張 … XD
過一年,也就是2016年,她在「校閱女王」中演重要的配角,這時候她的演技倒是進步不少,人是會成長的啊 ~ (下圖)
到這邊介紹了好幾個人了,再介紹最後一位,是主角三浦葵的前女友冴木瑠衣子 (市川由衣 飾),以外表來說是不如本田翼亮麗,但也相當漂亮,劇中她是個溫柔的女人,工作上的表現又相當不錯,這應該也是個相當有女性媚力的吧?! 這年頭有能力的女人也是相當吸引人的!
結尾了,看一下重逢後一個重要轉折,也是在這一刻結局就確定了!
一般來說,這種男人第一次偷偷親女人,女人是知道的,如果女人喜歡這個男人,就會繼續裝睡,算是一種默許吧 ~