2 days ago

Hi All, 這次稍微介紹一下SQL Exception處理,原由在於最近看到很多交易動作對於Exception的處理可以說是沒有,草草回傳Boolean是要別人去猜測為什麼會錯嗎?這樣的寫法是不好的,因此這一次我就稍微介紹一下SQL Exception Handle。

Dependencies

compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
testCompile group: 'junit', name: 'junit', version: '4.+'

def sqlVersion = '6.1.0.jre8'
compile group: 'com.microsoft.sqlserver', name: 'mssql-jdbc', version: "${sqlVersion}"

compile group: 'org.apache.commons', name: 'commons-dbcp2', version: "2.0"

FruitDao

這邊簡單的撰寫了一個FruitDao,主要方法為Insert動作而已,但大家可以看到mapFruitException的方法中,有對於SQL Error Code進行處理與包裝,讓其他開發者可以翻閱該Dao文件,即可瞭解這些錯誤訊息。

public class FruitDao {
    
    private BasicDataSource dataSource;
    
    public FruitDao() {
        dataSource = new BasicDataSource();
        dataSource.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
        dataSource.setUrl("jdbc:sqlserver://localhost;databaseName=andy");
        dataSource.setUsername("admin");
        dataSource.setPassword("admin");
    }
    
    public boolean insert(String name, int price) throws FruitException {
        int code = 0;
        
        String sql = "INSERT INTO FRUIT VALUES(?, ?)";
        
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        
        try {
            connection = dataSource.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, name);
            preparedStatement.setInt(2, price);
            
            code = preparedStatement.executeUpdate();
            System.out.println("Code: " + code);
        } catch (SQLException e) {
            System.out.println("Error code: " + e.getSQLState());
            throw mapFruitException(e);
        } finally {
            try {
                preparedStatement.close();
                connection.close();
            } catch (SQLException e) {
                throw mapFruitException(e);
            }
        }
        
        return code > 0;
    }
    
    private FruitException mapFruitException(SQLException sqlException) {
        int fruitErrorCode = 0;
        
        switch (sqlException.getErrorCode()) {
            case 515:
                fruitErrorCode = FruitException.IS_NULL;
                break;
            case 2601:
                fruitErrorCode = FruitException.REPEAT;
                break;
            default:
                fruitErrorCode = 0;
                break;
        }
        
        if(fruitErrorCode != 0) {
            return new FruitException(fruitErrorCode);
        } else {
            return new FruitException(sqlException.getMessage());
        }
    }
}

FruitException

對Exception進行擴充,加入ErrorCode位置,但這個ErrorCode是我們自定義的,目前包含null處理以及重複index的處理。

public class FruitException extends Exception {
    
    private static final long serialVersionUID = 1L;

    public static final int REPEAT = 0;
    
    public static final int IS_NULL = 1;
    
    private int errorCode = 0;
    
    public FruitException(String message) {
        super(message);
    }
    
    public FruitException(int errorCode) {
        this.errorCode = errorCode;
    }

    public int getErrorCode() {
        return errorCode;
    }
    
}

Test

FruitDao fruitDao = new FruitDao();
try {
    System.out.println(fruitDao.insert("Apple", 100));
} catch (FruitException e) {
  switch (e.getErrorCode()) {
    case FruitException.IS_NULL:
      System.out.println("欄位中出現NULL值,請確認欄位數值是否正確,");
      break;
    case FruitException.REPEAT:
      System.out.println("資料發現重複問題,請勿輸入重複資料。");
      break;
    default:
      System.out.println(e.getMessage());
      break;
  }
};

結語

這邊做了一個很簡單的試驗,但ErrorCode這種用法對於大型專案在執行會較好,小型專案就感覺殺雞用牛刀了,但我們也要記得丟出正確的錯誤訊息,讓使用這個Method的同仁或使用者可以瞭解為什麼錯誤,接下來我該如何處理等等。

希望大家可以慢慢培養錯誤處理的好習慣,儘管專案時程緊湊、繁重,該少的也是不能少,但說了那麼多屁話,希望長官還是要用真正的軟體工程思維進行管理,不要再用硬體思維進行管理,底下工城屍都死屍一片了...

Reference

Handling SQLExceptions
Java Code Examples for java.sql.SQLException

 
9 days ago

這幾個月工作實在太繁忙,沒有太多時間考慮Blog的更新,由於長期加班也在想,這麼努力工作,得到的回報是不是很值得呢?畢竟一直做重複性的工作,量多但是並沒有多大挑戰性,研究技術的時間整整被壓縮,這樣下去競爭力變差也不是公司的損失,是個人的損失吧?好了,今天就不多說了,要來介紹一下Spring中是如何使用Hibernate、JPA的。

然後我再多說題外話一下,有大大於FB敲我,這邊抱歉沒有回應,這幾個月有點忙,SORRY!!

蝦咪系 Hierbernate

Hibernate ORM是於JAVA上使用的一種ORM(Object Relational Mapping)工具,我覺得中文聽起來很憋扭啊!!物件對應關係、物件映射關係,隨便啦!!大家可以明白,這種工具就是將物件與物件的關係對應到資料表與資料表之間的關係,我們可以利用一些設定將這些物件關係設定好後,即可達到一些物件關聯的效果,這樣做我們也不需要去做這些SQL語法上的撰寫,也不需要去管理Connection,可以減少程式碼。

蝦咪系 JPA

JPA全名為Java Persistence API,他提供一個永續儲存的標準、規範,也就是這些ORM接口,Hibernate則為接口的實現,當然JPA跟Hibernate可能很像,因為JPA*致敬*了Hibernate才產生出來的嘛!!

開戰啦!!

Dependencies

//Spring

def springVersion = '5.0.1.RELEASE'
compile group: 'org.springframework', name: 'spring-context', version: "${springVersion}"
compile group: 'org.springframework', name: 'spring-orm', version: "${springVersion}"
compile group: 'org.springframework', name: 'spring-test', version: "${springVersion}"

//DBCP (Connection Pool)

compile group: 'org.apache.commons', name: 'commons-dbcp2', version: "2.0"

//Hibernate

def hibernateVersion = '5.2.10.Final'
compile group: 'org.hibernate', name: 'hibernate-core', version: "${hibernateVersion}"

//SQL Server

def sqlVersion = '6.1.0.jre8'
compile group: 'com.microsoft.sqlserver', name: 'mssql-jdbc', version: "${sqlVersion}"

//Log4j 2

def log4jVersion = '2.8.2'
compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: "${log4jVersion}"
compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: "${log4jVersion}"
compile group: 'org.apache.logging.log4j', name: 'log4j-jcl', version: "${log4jVersion}"

//common

compile group: 'commons-collections', name: 'commons-collections', version: '3.2'

//test

testCompile group: 'junit', name: 'junit', version: '4.+'

Configuration

以下將會介紹兩種設定方式,那麼基本上是一樣的,但目前個人偏好JavaConfig方式進行,好處在於找問題時連Spring Boot的文章也可以稍微看看,畢竟Spring Boot是沒有XML設置的。

Java Config Spring Configuration

@Configuration
@EnableTransactionManagement
public class PersistenceJpaConfig {

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactory.setDataSource(dataSource());
        entityManagerFactory.setPackagesToScan("com.spring.example");
        
        JpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
        entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter);
        
        Properties properties = new Properties();
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.SQLServer2012Dialect");
        properties.setProperty("hibernate.show_sql", "true");
        entityManagerFactory.setJpaProperties(properties);
        
        return entityManagerFactory;
    }
    
    @Bean
    public BasicDataSource dataSource() {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
        dataSource.setUrl("jdbc:sqlserver://localhost;databaseName=andy");
        dataSource.setUsername( "admin" );
        dataSource.setPassword( "admin" );
          
        return dataSource;
    }
    
    @Bean
    public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory);
        return transactionManager;
    }
    
    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
    }
    
    @Bean
    public FruitDao fruitDao() {
        FruitDao fruitDao = new FruitDaoImpl();
        return fruitDao;
    }
    
    @Bean
    public FruitBo fruitBo() {
        FruitBoImpl fruitBo = new FruitBoImpl();
        fruitBo.setFruitDao(fruitDao());
        return fruitBo;
    }
    
}

XML Spring Configuration

<?xml version="1.0" encoding="UTF-8"?>
<beans 
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-4.1.xsd">  

    <context:component-scan base-package="com.spring.example.*" />
        
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
      <property name="dataSource" ref="dataSource" />
      <property name="packagesToScan" value="com.spring.example" />
      <property name="jpaVendorAdapter">
         <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
      </property>
      <property name="jpaProperties">
         <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.SQLServer2012Dialect</prop>
         </props>
      </property>
   </bean>

    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
        <property name="url" value="jdbc:sqlserver://localhost;databaseName=andy" />
        <property name="username" value="admin" />
        <property name="password" value="admin" />
    </bean>
    
    <tx:annotation-driven transaction-manager="transactionManager" />
    
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" /> 
    </bean>

    <bean id="persistenceExceptionTranslationPostProcessor" class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
 
   <bean id="fruitBo" class="com.spring.example.FruitBoImpl" >
        <property name="fruitDao" ref="fruitDao" />
   </bean>

   <bean id="fruitDao" class="com.spring.example.FruitDaoImpl" />

</beans>

Entity

方才說過ORM就是將物件與物件的關係對上資料庫,因此我們這邊設定方法就是一個Object對上一張Table。

Fruit

水果Der物件對上Table名稱為FRUIT的資料表,在Hibernate底下是一定要有PK的,然而資料庫本身確實就該有PK或者多欄位組成的PK,這邊PK很簡單的就設定為流水號,剩餘的就是哪個欄位對上哪個參數。

@Entity
@Table(name = "FRUIT")
public class Fruit {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    
    @Column(name = "NAME")
    private String name;
    
    @Column(name = "PRICE")
    private int price;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Fruit [id=" + id + ", name=" + name + ", price=" + price + "]";
    }
    
}

Data Access Object

FruitDao

public interface FruitDao {

    public void save(Fruit fruit);
    
    public void update(Fruit fruit);
    
    public void delete(Fruit fruit);
    
    public List<Fruit> getFruits();
    
}

FruitDaoImpl

這邊我稍微講一下,如果會使用到新增、刪除、修改之餘,就要加上Transactional Annotation告訴JPA我們的動作。

@Repository
public class FruitDaoImpl implements FruitDao {

    @PersistenceContext
    private EntityManager entityManager;
    
    @Transactional
    public void save(Fruit fruit) {
        entityManager.persist(fruit);
    }

    @Transactional
    public void update(Fruit fruit) {
        entityManager.merge(fruit);
    }

    @Transactional
    public void delete(Fruit fruit) {
        entityManager.remove(fruit);
    }

    @SuppressWarnings("unchecked")
    public List<Fruit> getFruits() {
        return entityManager.createQuery("Select f from Fruit f").getResultList();
    }

}

Business Object

FruitBo

public interface FruitBo {
    
    public void save(Fruit fruit);
    
    public void update(Fruit fruit);
    
    public void delete(Fruit fruit);
    
    public List<Fruit> getFruits();
    
}

FruitBoImpl

public class FruitBoImpl implements FruitBo {

    private FruitDao fruitDao;
    
    public void setFruitDao(FruitDao fruitDao) {
        this.fruitDao = fruitDao;
    }
    
    public void save(Fruit fruit) {
        fruitDao.save(fruit);
    }

    public void update(Fruit fruit) {
        fruitDao.update(fruit);
    }

    public void delete(Fruit fruit) {
        fruitDao.delete(fruit);
    }

    public List<Fruit> getFruits() {
        return fruitDao.getFruits();
    }

}

Test

@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(locations = "classpath:hibernate.cfg.xml")

@ContextConfiguration(classes = PersistenceJpaConfig.class)
public class HibernateTest extends AbstractJUnit4SpringContextTests {
    
    @Autowired
    private FruitBo fruitBo; 
    
    @Test
    public void hibernateTest() {
        
        Fruit fruit = new Fruit();
        fruit.setName("Strawberry");
        fruit.setPrice(300);
        
        fruitBo.save(fruit);
        
        if(fruitBo.getFruits().size() > 0) {
            assertTrue(true);
        } else {
            assertTrue(false);
        }
        
    }
    
}

Reference

Hibernate (framework)
淺談【物件關係對映(Object Relational Mapping ORM)】
A Guide to Hibernate with Spring 4

 
about 2 months ago

What is Token ?

Token就是一種身分證的樣子,當使用者登入以後,我們可以使用uid、time、sign等資訊,進行一連串的加密動作,來得到該使用者的Token,並且返回給客戶端,後面客戶端將會用該Token進行一連串的操作,如Rest API。

無狀態性(Stateless)

Token本身是無狀態性的,所以當有客戶端傳送一個Token進來,這時後伺服器將會進行驗證動作,確認是不是過期了,Token是否被惡意串改過了。

安全性提高

現在我們需要客戶端主動給我們Token,可以避免跨網域請求偽造攻擊(CSRF),雖然Token安全性比Session更高,但是如果你今天讓別人發現你的Token後,那麼你的身分將有可能被盜用。

除了上述的部分外,通常Token組合是使用者資料、日期以及自己的私鑰進行HASH動作,因此要破解,要嘛就是我們的私鑰流出去,不然就是人家很有心的使用暴力破解法,這樣來取得個資,但是,通常我們不會把私密訊息放入Token的組合,儘管他破解了,我們也還能夠進行網域、IP的驗證。

使用情境

通常Token可以使用在不同系統間的資源交換,這種無狀態的身分證會比Session更加的安全,使用最多的都是Mobile App,由於Mobile並沒有Cookie,這時候我們就會選擇使用Token進行。

Token都會有期限的,那麼設計上也都是這樣,因此我們如果使用者閒置過久,大部分會需要你重新登入,但這樣又影響使用者體驗,因此當用戶的請求來的時候,我們可以先確認該Token是不是由我們所發出去的,並且將該Token的時間期限延長。

 
2 months ago

SlickGrid套件在Bootstrap3所遇到的問題

SlickGrid在Jquery套件表現非常搶眼,但也似乎是因為年久失修,造成與Bootstrap3的css衝突問題,這部分的解決方式在這裡可以看到Conflict with bootstrap 3 #742,那麼這上面有許多解法,這邊我個人是推薦下面方式。

修改CSS

[class^="slickgrid_"],
[class^="slickgrid_"] div {
  -webkit-box-sizing: content-box;
     -moz-box-sizing: content-box;
          box-sizing: content-box;
}

修改slick.grid.js

如果你的Grid元件想要讓使用者可resize那麼修改applyColumnHeaderWidths方法是一定要的,否則在拖拉的時候,又會不對齊了,但這方法只適用jQuery1.8以後的版本。

if (h.outerWidth() !== columns[i].width - headerColumnWidthDiff) {
    h.outerWidth(columns[i].width - headerColumnWidthDiff);
}
 
3 months ago

What is AOP ?

Introduction

Spring Aspect-Oriented Programming簡稱為AOP,這個模組提供了另外一種思考模式,經由實現crosscutting concerns組成Aspect,並且來達到類別與對象之間的事務管理,如日誌系統等等。

正常我們沒有使用Aspect的情況下,會將crosscutting concerns埋入商業邏輯中,這樣其實當有修改這個邏輯需求時,是有可能需要移除或者新增與修改crosscutting concerns的動作,若今天使用Spring AOP模組,我們可以將crosscutting concern完全拉出來進行撰寫,儘管後續修改商業邏輯,也不太需要理會crosscutting concern的物件。

AOP concepts(概念)

在Spring官方說明,這些AOP的術語並不是他們所進行訂製的,但也是非常的難以理解,下面將簡單介紹。

Aspect

Aspect最大的作用即是收集各商務物件中的crosscutting concerns。在Spring中除了使用XML設定方式,也建議可以使用Annotation方式進行開發,會較於簡單。

Join point

Aspect在應用程式執行時加入商務流程的點或時機稱之為Joinpoint,被呼叫的時機有可能在某個方法的前面(@before)或後面(@after),然而Joinpoint還可以讓我們取到傳入該流程的參數以及結果,也能夠攔截例外處理。

Advice

顧名思義,Advice將會處理有使用“around”、“before”和“after”等不同類型的通知,這部分後面看程式碼會較於清楚。

Pointcut

這部分可以看成,對於哪一個class的某個method進行關聯的動作,讓Advice可以知道我們要從哪個method進行通知處理。

Introduction

這不要翻譯成介紹,稱之為引入較為適當,在Java很不幸的我們無法動態的修改已編譯過的類別,但是Introduction卻可以讓你動態為這個以編譯過的類別進行新增功能等動作,確實是滿驚人的,不過我沒有試過,有興趣的大大歡迎試試看。

Target object

一個Advice被應用的對象或目標物件,如果直接使用Target Object進行撰寫橫切點等程式碼,會發現使用這種方式進行的crosscutting concerns都是需要自己設定ProxyBean,這實在是非常麻煩,但若使用Aspect則可以達到自動代理的機制。

AOP proxy

AOP框架創建的對象,用來實現Aspect。然而Spring AOP是使用動態代理的方式進行。然後代理的理解可以看看這個從代理機制初探 AOP,這邊有個簡單的小程式讓大快速理解代理機制。

Weaving

Advice被應用到物件上的過程稱為Weaving,在AOP中縫合的方式有幾個時間點:編譯時期(Compile time)、類別載入時期(Classload time)、執行時期(Runtime)。(取自於:AOP 觀念與術語)

Advice 通知的類型

@Before advice

在某連接點之前執行的通知,但不能令Join point停止執行,除非發生異常。

@After return advice

在某個接點執行成功,並且返回值得時候觸發。

@After throwing advice

在方法拋出異常退出時執行的通知。

@After(final) advice

某個接點退出的時候執行,不管接點是否異常。

@Around advice

這個功能算是滿常用的,上述的通知類型都會執行。

實戰

剛剛上面簡單的介紹了AOP,那麼現在我們這邊將要進行實作了,讓大家包括我可以更了解AOP的使用方法與情境。

目錄架構

Gradle

這邊會看到我們加入了aspects模組。

apply plugin: 'java'
apply plugin: 'eclipse'

sourceCompatibility = 1.8
version = '1.0'
jar {
    manifest {
        attributes 'Implementation-Title': 'Gradle Quickstart',
                   'Implementation-Version': version
    }
}

repositories {
    mavenCentral()
}

dependencies {
    //Spring

    def springVersion = '4.1.6.RELEASE'
    compile group: 'org.springframework', name: 'spring-context', version: "${springVersion}"
    compile group: 'org.springframework', name: 'spring-aspects', version: "${springVersion}"
    compile group: 'org.springframework', name: 'spring-test', version: "${springVersion}"
    
    //Log4j 2

    def log4jVersion = '2.8.2'
    compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: "${log4jVersion}"
    compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: "${log4jVersion}"
    compile group: 'org.apache.logging.log4j', name: 'log4j-jcl', version: "${log4jVersion}"

    //common

    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'

    //test

    testCompile group: 'junit', name: 'junit', version: '4.+'
}

test {
    systemProperties 'property': 'value'
}

uploadArchives {
    repositories {
       flatDir {
           dirs 'repos'
       }
    }
}

ContextConfig.java

這個類別就是這麼簡單,這部分其實就是取代了傳統xml的部分,今天其實也不用特地創個類別寫,但是為了整體的程式碼乾淨以及後續的擴充,通常我會特別拉出來創建,這邊做的事情有幾件事情需要了解。

  1. @Configuration 代表這是一個SpringConfig,若使用ApplicationContext讀取該類別,將會類似我們上一章的xml所設定的事情。
  2. @ComponentScan 這部分就可以指定我們所要Scan的Package,這邊後面會稍微在講解。
  3. @EnableAspectJAutoProxy 開啟讓Aspect自動代理。
@Configuration
@ComponentScan("com.spring.example.*")
@EnableAspectJAutoProxy
public class ContextConfig {

}

User.java

public class User {
    
    private String userName;
    
    private String nickName;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    @Override
    public String toString() {
        return "User [userName=" + userName + ", nickName=" + nickName + "]";
    }
    
}

UserDao.java

剛剛上面有ComponentScan的注入,這邊大家可以看到我在類別上面使用了@Component,這部分其實就是跟我們在xml使用所創建出來的bean是一樣的。

@Component
public class UserDao {
    
    public User getUserById(int id) {
        System.out.println("Method getUserById() called");
        return new User();
    }

    public int setUser(User user) {
        System.out.println("Method setUser() called");
        return 0;
    }

    public int delUserById(int id) {
        return 0;
    }
    
}

UserAspect.java

今天的主角出現了,文章開頭就介紹許多關於AOP的術語與觀念,這邊有看到@Before、@After,這兩個就是Advice通知類型,在這個Advice後面大家可以看到execution以及指定類別、方法,這邊就是Pointcut。

@Aspect
@Component
public class UserAspect {

    @Before("execution(* com.spring.example.dao.UserDao.getUserById(..))")
    public void logBeforeV1(JoinPoint joinPoint) {
        System.out.println("UserAspect.logBeforeV1() : " + joinPoint.getSignature().getName());
    }

    @Before("execution(* com.spring.example.dao.UserDao.*(..))")
    public void logBeforeV2(JoinPoint joinPoint) {
        System.out.println("UserAspect.logBeforeV2() : " + joinPoint.getSignature().getName());
    }

    @After("execution(* com.spring.example.dao.UserDao.getUserById(..))")
    public void logAfterV1(JoinPoint joinPoint) {
        System.out.println("UserAspect.logAfterV1() : " + joinPoint.getSignature().getName());
    }

    @After("execution(* com.spring.example.dao.UserDao.*(..))")
    public void logAfterV2(JoinPoint joinPoint) {
        System.out.println("UserAspect.logAfterV2() : " + joinPoint.getSignature().getName());
    }

}

AopTest

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {ContextConfig.class})
public class AopTest extends AbstractJUnit4SpringContextTests{

    @Resource
    private UserDao userDao;
    
    @Test
    public void aopTest() {
        
        userDao.getUserById(1);
        userDao.setUser(new User());
        
    }
    
}

Result

當大家執行AopTest時,就可以在Console看到這幾行。

UserAspect.logBeforeV1() : getUserById
UserAspect.logBeforeV2() : getUserById
Method getUserById() called
UserAspect.logAfterV1() : getUserById
UserAspect.logAfterV2() : getUserById
UserAspect.logBeforeV2() : setUser
Method setUser() called
UserAspect.logAfterV2() : setUser

結語

AOP是一個強大的模組,使用它可以讓crosscutting concerns動作解耦,當產品上線後,維運就是必要的,若能夠讓這些動作切開來,令商業邏輯的流程乾淨,是非常棒的一件事情,再加上使用Annotation方式進行設定,大家應該會發現比傳統的xml設定清楚、乾淨吧!

希望這邊簡單的文章對大家有所幫助!!

Reference

AOP 觀念與術語
Aspect Oriented Programming with Spring
Spring 实践:AOP

 
3 months ago

What is Spring ?

Spring是一個lightweight(輕量級)的framework(框架),它可以想像是各種框架的集成,因為它可以支援許多種框架,例如Struts, Hibernate, Tapestry, EJB, JSF等等,它包含幾種模組,例如IOC, AOP, DAO, Context, ORM, WEB MVC等等,不過我們第一步需要先了解IOC(控制反轉)以及Dependency Injection(依賴注入)。

What is IOC、Dependency Injection ?

Spring容器實現了IOC,也是這整個框架的核心所在,容器將會創建、管理物件直到銷毀,並使用Dependency Injection(依賴注入)來管理這些物件(Bean),然而Spring提供這兩種IOC容器,分別為BeanFactory、ApplicationContext,這兩部分的差別,後將依序簡單的探討。

BeanFactory
這是Spring提供最簡單的IOC容器,是無法使用大部分模組的,例如AOP、Integration、Web等等,因此僅適合在有限資源的環境下使用,通常用於測試與非生產環境。

ApplicationContext
大神也推薦使用這個IOC容器,該容器繼承BeanFactory並提供了更多的功能,因此我們可以方便的使用各種模組,來幫助開發。

Hello Spring

上面稍微介紹了Spring的核心功能,那麼我們現在來給它實作看看,應該會更加了解Spring容器。

目錄架構

Gradle

Gradle這邊我們只要引入spring-context即可,這個lib是Spring的核心,其他引入的部分並沒有使用到,只是我直接copy其他部分的程式碼XD。

apply plugin: 'java'
apply plugin: 'eclipse'

sourceCompatibility = 1.8
version = '1.0'
jar {
    manifest {
        attributes 'Implementation-Title': 'Gradle Quickstart',
                   'Implementation-Version': version
    }
}

repositories {
    mavenCentral()
}

dependencies {
    //Spring

    def springVersion = '4.1.6.RELEASE'
    compile group: 'org.springframework', name: 'spring-context', version: "${springVersion}"
    
    //Log4j 2

    def log4jVersion = '2.8.2'
    compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: "${log4jVersion}"
    compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: "${log4jVersion}"
    compile group: 'org.apache.logging.log4j', name: 'log4j-jcl', version: "${log4jVersion}"

    //common

    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'

    //test

    testCompile group: 'junit', name: 'junit', version: '4.+'
}

test {
    systemProperties 'property': 'value'
}

uploadArchives {
    repositories {
       flatDir {
           dirs 'repos'
       }
    }
}

beans.xml

Spring最經典的就是使用xml設定了,那麼其實有另外一種JavaConfig設定方式,我還在考慮要不要放XDD

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN" 
  "http://www.springframework.org/dtd/spring-beans.dtd"> 
<beans> 
    <bean id="helloBean" class="com.spring.example.HelloSpringBean"> 
        <property name="message">
            <value>Hello Spring!!</value>
        </property> 
    </bean> 
</beans>

HelloSpringBean.java

這邊大家可以對應到上面的XML設定,會發現我們設定了class位置,並命名id為helloBean以及設定message為Hello Spring!!,然而這邊提醒一下,大家還是要習慣加入toString,會比較方便測試。

public class HelloSpringBean {

    private String message;
    
    public void setMessage(String message) {
        this.message = message;
    }
    
    public String getMessage() {
        return message;
    }

    @Override
    public String toString() {
        return "HelloSpringBean [message=" + message + "]";
    }
    
}

HelloSpringApp.java

這裡就是整個Spring容器啟動的時候,我們可以看到我使用了ClassPathXmlApplicationContext的ApplicationContext類別來加入上述所設的beans.xml,並且取出id為helloBean的物件。

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class HelloSpringApp {

    private static Logger logger = LogManager.getLogger(HelloSpringApp.class);

    @SuppressWarnings("resource")
    public static void main(String[] args) {
        
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        
        HelloSpringBean helloSpringBean = (HelloSpringBean) context.getBean("helloBean");
        
        logger.info(helloSpringBean);
    }
  
}

Result Console

大家可以看看最後一行,是不是出現我們剛剛設定的message了呢?

22:56:15.943 [main] INFO  org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@43bd930a: startup date [Tue Sep 05 22:56:15 CST 2017]; root of context hierarchy
22:56:15.973 [main] INFO  org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [beans.xml]
22:56:16.049 [main] INFO  com.spring.example.HelloSpringApp - HelloSpringBean [message=Hello Spring!!]

結語

Spring整個核心圍繞著IOC、DI使用,不論加上哪一種模組,我們都需要設定(Xml or JavaConfig),很多人雖然使用著Spring開發,但卻IOC、DI非常的陌生,更不用說BeanFactory的生命週期了,了解基礎的東西才能讓我們更快速地排除問題,今天這篇文章非常的簡單,我甚至還沒有深入到BeanFactory整個產生Bean的過程,這部分就牽扯到底層的部分。

寫這系列文也是希望自己不要只會開發,也要了解底層的做法,藉此鞭策自己持之以恆的學習,軟體真的是個坑...學起來放(老屁股之路? XD)。

Reference

Spring Tutorial
開源框架: Spring Gossip
极客学院 - IoC 容器

 
about 1 year ago

Question

Given two binary trees, write a function to check if they are equal or not.

Two binary trees are considered equal if they are structurally identical and the nodes have the same value.

這到題目要我們判斷兩個Tree是否相同。

Resolve

這題的解法我們需要依序去判斷兩個tree是否為空,優先判斷若是空值就先回傳true or false,後者在判斷val值是否正確,因此這提示不困難的,但鄙人目前只會土法煉鋼,程式碼大概如下。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        
        if(p == null && q == null) {
            return true;
        }
        
        if(p == null && q != null || p != null && q == null) {
            return false;
        }
        
        if(p.val != q.val) return false;
        
        boolean res = isSameTree(p.left, q.left);
        
        if(!res) return false;
        
        res = isSameTree(p.right, q.right);
        
        if(!res) return false;
        
        return true;
       
    }
}
 
over 1 year ago

醜陋的數字呀! 數字中有質因數2 3 5便判斷為醜陋的數,假設今天並沒有全部符合2 3 5代表並非醜陋數

而1,我們通常都會判定為醜陋的數。

public class Solution {
    public boolean isUgly(int num) {
        while (num >= 2) {
            if (num % 2 == 0) num /= 2;
            else if (num % 3 == 0) num /= 3;
            else if (num % 5 == 0) num /= 5;
            else return false;
        }
        return num == 1;
    }
}

程式碼解說:

通常我們1都醜陋數,因此我們從2開始算,每次運算我們都會讓num去取2 3 5的餘數,假設為0變取代目前的num,

這算是一種簡單的作法,如果使用for迴圈去算可能會需要很多時間,因此這樣的作法是比較正確的。

假設我們取到一個不屬於2 3 5的質因數,那麼我們當然就是口愛的數字惹,也將不必再運算,直接給他傳回去。

 
over 1 year ago

兩數相加不使用+/-來達成,其實很簡單的,直接位元運算,但我們常常忘記位元運算這種東西,包括我也沒有做過這樣的運算。

這個程式碼是我目前覺得最好的答案,簡單又清楚,速度上也可以接受。

以下可以簡單解釋一下程式碼

public class Solution {
    public int getSum(int a, int b) {
        while(b!=0) {
            int c = a & b; 
            a = a ^ b; 
            b = c << 1; 
        }
        return a;
    }
}

其實就是很純粹的位元運算,下面用簡單的方式圖解一下。

假設a = 5, b = 2, answer = 7;

a = 0101
b = 0010

(1)
c = a & b = 1000
a = a ^ b = 0111
b = c << 1 = 0000
b = 0, 回傳a, 答案為(0111)

這是很簡單的解法,利用位移讓我們達到相加的道理,然後計算基本上就是使用0與1,其實這才是真正的加法!

 
over 1 year ago

肥宅最近工作繁忙,也已經很久沒有寫部落格了,應該會開始慢慢回復以前的情形,也許1~2禮拜一篇文章之類的。
工作上的需求常常會使用到MySQL,然而在SQL上,想要正規化資料庫,那關聯肯定是其中最不可或缺的因素之一。
這邊分享一下關聯的簡單用法,主要是使用InnerJoin其他種Join目前還沒有榮幸去研究。

假設我們這邊有三個表格依序為table1, table2, table3;

id country
1 台灣
2 美國
3 日本
id c_id city
1 1 高雄
2 1 台中
3 3 東京
id c_id food
1 1 豬血糕
2 2 麵線
3 3 黑輪

我通常都會創建一個檢視表,放便以後讀取,不需要每次都寫SELECT。

CREATE VIEW food_posts AS SELECT table3.id, table3.food, table2.city, table1.country
FROM table3
INNER JOIN table2 ON table3.c_id = table2.id
INNER JOIN table3 ON table2.c_id = table1.id
WHERE table1.city = '台灣';

這樣就可以創見一個所有台灣美食的檢視表,但是要稍微注意一下Join的順序,使用正規化方式所開發的資料庫會較於維護,但要注意一點,若要將上層刪除掉,下層相關的也要一併刪除,否則怕以後擷取資料會有錯誤。