18 days ago

大給賀,肥宅今天要分享那個Redis啦!!

Redis是一個OpenSource的CacheServer,人家可是NoSQL + KeyValue概念呢! 想當然Cache有分內存、磁碟等兩種方式,要使用哪一種儲存方式全看需求。然而Redis身為一個社群活耀的OpenSource,那麼內建transactions、replication以及data structures等多樣功能肯定也是很正常的一件事情。

話不多說,進入主題,窩們今天要在centos7上面給他裝裝Redis囉!

1. Yum Install

最簡單的安裝方式。

Step 1. Update yum and install EPEL repository

yum install epel-release
yum update

Step 2. Install Redis

yum install redis

# 開機自動啟動囉!
systemctl enable redis

Step 3. Configuration redis.conf

使用這種方式安裝的設定檔可於/etc/redis.conf看到,我們需要編輯他。

vi /etc/redis.conf

#----加入以下資訊----#

#加入對外IP,若沒有加可以於local測試,反之則無法於外面機器測試
bind 127.0.0.1 192.168.56.101

2. Download Install

如果你不想使用yum進行安裝的人,可以使用這個方式。

Step 1. Install necessary packages

Redis有使用到其他的Source,因此一並給他裝上去。

yum install wget make gcc

Step 2. Install Redis

mkdir /usr/redis
wget http://download.redis.io/releases/redis-3.2.9.tar.gz // download
tatar -zxvf redis-3.2.9.tar.gz -C /usr/redis --strip-components=1 // untar
make && make install

Step 3. Install Init Redis Script

在這個步驟你會需要輸入redis的相關設定,那基本上我是都照預設下去跑,一直Enter。

cd /usr/redis/utils
./install_server.sh

# 開機自動啟動囉!
systemctl enable redis_6379

Step 4. Configuration redis.conf

若是像上述直接enter安裝的你,會在/etc/redis/6379.conf發現redis設定檔,我們需要編輯他。

vi /etc/redis/6379.conf

#----加入以下資訊----#

#加入對外IP,若沒有加可以於local測試,反之則無法於外面機器測試
bind 127.0.0.1 192.168.56.101

Testing

我們就簡單測試看看,理論上會儲存成功,並且Get出123字串。

redis-cli set andy '123'
redis-cli get andy
 
20 days ago

大給賀,最近發文很不固定,工作的事情非常忙,誰叫窩是扛霸子呢? ((誤

今天要來給大家介紹Nginx與Tomcat如何做Load Balance呢?

在這邊大家一定會想到Apache,沒錯! Apache也能使用proxy、mod_jk達到相同效果,但Apache當Load Balance Server能力並沒有Nginx還要快。那麼說Apache功能就沒了嗎?別忘記Apache功能方面比Nginx多,若今天只需要一台Load Balance Server的話,那麼可以考慮Nginx。

老實說這也是我第一次使用Nginx,單由設定proxy功能來看,Nginx相對簡單呢!話不多說,來看看我的步驟。

Nginx Setting

Step 1. Install Nginx

yum install epel-release
yum install nginx

Step 2. Open Firewall

基本上linux 0~1023的埠號都是有定義的,如下顯示的http與https分別代表80、443等兩個埠號。

firewall-cmd --permanent --zone=public --add-service=http 
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --reload

Step 3. Start Nginx

systemctl enable nginx
systemctl start nginx

Step 4. Setting nginx.conf

我們可以修改/etc/nginx/nginx.conf設定,來達到Load Balance效果,基本上大家只需要專注在upstream以及server這兩個標籤的設定即可。

# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;
    
    upstream cluster_server {
        server 192.168.56.102:8080;
            server 192.168.56.103:8080;
    }

    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  tomcat.cluster.com;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
        proxy_pass http://cluster_server;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
}

Step 5. Proxy Permission denied

小弟我在執行過程中遇見這個問題,那麼解決方式只要使用以下指令,打開權限即可。

setsebool -P httpd_can_network_connect 1

Step 6. Restart Nginx

systemctl restart nginx

Tomcat Setting

我沒有設定Tomcat呢...我只是要做最簡單的Load Balance,因此Tomcat不理他...然後Tomcat安裝方式網路上有很多!! XD

Reference

How To Install Nginx on CentOS 7
Using nginx as HTTP load balancer
Nginx與Apache比較
(13: Permission denied) while connecting to upstream:[nginx]

 
2 months ago

肥宅遇到今天的這個問題,其實肥宅也不大了解,但是窩有Google師傅幫忙,下面是我查到的方式。

舉例來說,當使用rm進行刪除檔案後,若今天檔案是10G的大小,那麼如果使用df -h查看硬碟容量,會發現空間未被釋放。那麼這種情況就是因為我們正有process使用該檔案,因此硬碟空間並未釋放空間。

若發上上述的情境問題,我們可以嘗試以下指令

lsof | grep deleted

上述的指令作用在於,取得目前正在運行的process與對應使用的檔案(被刪除),我們可以透過這個指令了解是哪一個process佔住了空間,我們需要對該process進行停止或者暫停來讓硬碟釋放資源。

很簡單,我沒有圖可以讓你們看圖說故事,因為我懶了 Orz

 
3 months ago

最近公司有使用到這東西,這東西說真的...太好用啦!!!!

接下來跟大家介紹簡單的Spring Integration的應用,包含UDP與TCP方式。

Spring Integration 系蝦咪?

Integration 顧名思義是一種整合或集成的意思,有經驗的工程師不難都知道EAI(Enterprise Application Integration)的相關知識,EAI主要是對於內外部系統進行整合來達到各種應用程式的資料交換,大家可以看看簡單的架構圖 ↓

是不是太簡單了,↑ 它並不是主角(其實是自己還沒真的遇到這種架構),那基本上是這樣架構,各種應用程式都整合到EAI中,但EAI裡面會包含應用整合、訊息整合、交換格式統一等等,這些都是EAI必然去規範與實作的,那麼更詳細的EAI內容大家可以去網路上求大神取經,這年頭Know How很重要啊!!

接下來讓我們簡單說明一下Spring Integration的一些使用名稱,這邊我就不做多餘的解釋,如何使用得部分請看下一章。

Main Component

Message

- Message是Integration基礎建構,所有的流程都會圍繞著它運行。
- Message由payload以及header所組成。

Message Channel

- Message Channel將分離Message Component並提供攔截點,方便攔截使用。簡而言之,它就是一個通道。

Message Endpoints

Transformer

- Message Transformer可以轉換Payload格式以及修改標頭並回傳修改後的Message。

Filter

- Message Filter決定是否應該將資料傳送至某個Output Channel,若是消息被Filter接受了,那將會傳送至Output Channel,反之將會丟失該筆資料。

Route

- Message Router 決定哪一個頻道應該接收Message,通常是使用Payload以及Header去決定下一步驟。

Service Activator

- Service Activator 可以調用服務來處理輸入訊息,並將返回的數據發送至輸出通道。

Channel Adapter

- Channel Adapter是將Message Channel連接到某個系統的端點。

結語

每次寫這種簡單的介紹文章,整個就是超無聊,但是卻很重要QQ

Refenrence

Spring Integration Overview
WIKI - 企業應用整合
EAI技術和概念解析

 
3 months ago

TCP跟UDP最大的差別就是驗證,它會反覆確認資料是否正確,若資料不正確會要求重新送封包直到資料正確為止,因此這個步驟會花上一點時間。然而當可靠性大於時效性時,大多會使用TCP方式來進行連線,反之。

Integration Config

不得不說TCP設定麻煩了許多,這邊我將介紹直接寫在程式碼註解上會比較好理解。

@Configuration
@EnableIntegration
public class TcpIntegrationConfig {

    // 訊息處理邏輯

    @Bean
    public TcpMessageHandler tcpMessageHandler() {
        return new TcpMessageHandler();
    }
    
  // Connection資料設定,這邊要注意Serializer與Deserializer,跟通訊協定有關係

    @Bean
    public AbstractConnectionFactory tcpNetServerConnectionFactory() {
        TcpNetServerConnectionFactory tcpNetServerConnectionFactory = new TcpNetServerConnectionFactory(1234);
        tcpNetServerConnectionFactory.setSerializer(new ByteArrayRawSerializer());
        tcpNetServerConnectionFactory.setDeserializer(new ByteArrayRawSerializer());
        tcpNetServerConnectionFactory.setSingleUse(true);
        return tcpNetServerConnectionFactory;
    }
    
  // 輸入通道

    @Bean
    public MessageChannel inChannel() {
        return new DirectChannel();
    } 
    
  // 輸出通道

    @Bean
    public MessageChannel outChannel() {
        return new DirectChannel();
    } 
    
  // TCP Channel Adapter,將這個端點的輸出通道給inChannel並且指定上面所設定的連線資訊

    @Bean
    public TcpReceivingChannelAdapter tcpInboundChannelAdapter() {
        TcpReceivingChannelAdapter tcpReceivingChannelAdapter = new TcpReceivingChannelAdapter();
        tcpReceivingChannelAdapter.setOutputChannel(inChannel());
        tcpReceivingChannelAdapter.setConnectionFactory(tcpNetServerConnectionFactory());
        return tcpReceivingChannelAdapter;
    }
    
  // 有接受就有送出,TcpSendingMessageHandler其實就是out bound的部分

    @ServiceActivator(inputChannel = "outChannel")
    @Bean
    public TcpSendingMessageHandler out(AbstractConnectionFactory connectionFactory) {
        TcpSendingMessageHandler out = new TcpSendingMessageHandler();
        out.setConnectionFactory(connectionFactory);
        return out;
    }
    
  // 這邊不太好解釋,大致上是說該端點資料取得後,丟到tcpMessageHandler進行處理,並且回傳資訊。

    @Bean
    public IntegrationFlow integrationFlow() {
        return IntegrationFlows
                .from(tcpInboundChannelAdapter())
                .handle(tcpMessageHandler())
                .channel("outChannel")
                .get();
    }
    
}

MessageHandler

這邊使用AbstractReplyProducingMessageHandler,這個物件可以幫助我們回傳資訊

public class TcpMessageHandler extends AbstractReplyProducingMessageHandler {

    private static Logger logger = LogManager.getLogger(TcpMessageHandler.class);
    
    @Override
    protected Object handleRequestMessage(Message<?> requestMessage) {
        String data = new String((byte[]) requestMessage.getPayload(), StandardCharsets.UTF_8);
        logger.info(data);
        return data;
    }

}

Socket

public class TcpClient {
    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        
        @SuppressWarnings("resource")
        Socket client = new Socket("localhost", 1234);
        
        String sentence = in.readLine();
        byte[] sendData = sentence.getBytes();
        
        BufferedOutputStream outputStream = new BufferedOutputStream(client.getOutputStream());
        outputStream.write(sendData);
        outputStream.flush();
        client.shutdownOutput();
        
        BufferedInputStream inputStream = new BufferedInputStream(client.getInputStream());
        byte[] b = new byte[1024];
        String data = "";
        int length;
        while ((length = inputStream.read(b)) > 0) { // <=0的話就是結束了

            data += new String(b, 0, length);
        }
        System.out.println(data);
        
        client.close();
    }
}

App

public class TcpApp {
    public static void main(String[] args) {
        @SuppressWarnings("resource")
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TcpIntegrationConfig.class);
        context.start();
    }
}

Result

大家可以看到運行結果,Client丟一個Hello World字串回去,Server接收到後會原封不動丟回來。

Refenrence

WIKI - 企業應用整合
EAI技術和概念解析

 
3 months ago

首先我們介紹UDP,咱們UDP大神就是秉持著射後不理的態度進行傳輸,完完全全的快速,但射後不理有可能造成資料遺失,又但是呢~用這種UDP的應用,就是時效性大於可靠性時的應用才會大力使用。

Dependencies

//Spring

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

def springIntVersion = '5.0.0.RELEASE'
compile group: 'org.springframework.integration', name: 'spring-integration-core', version: "${springIntVersion}"
compile group: 'org.springframework.integration', name: 'spring-integration-ip', version: "${springIntVersion}"

//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.+'

Integration Config

首先,大家可以看到我們有一個ChannelAdapter,它是作為系統與系統連接的端點,我們將這個端點的輸出通道設定一個OutputChannel,到這邊以後,我們可以簡單利用DLS進行IntegrationFlow的設定,整個UDP Integrationg 設定如下:

@Configuration
@EnableIntegration
public class UdpIntegrationConfig {
    
  // 處理接收到資料的邏輯

    @Bean
    public UdpMessageHandler udpMessageHandler() {
        return new UdpMessageHandler();
    }
    
  // 輸出通道

    @Bean
    public MessageChannel outputChannel() {
        return new DirectChannel();
    } 
    
  // ChannelAdapter為系統與系統間的端點,將這端點的輸出通道設為上面的outputChannel,我們將可以利用該通道取得資料

    @Bean
    public UnicastReceivingChannelAdapter udpInboundAdapter() {
        UnicastReceivingChannelAdapter unicastReceivingChannelAdapter = new UnicastReceivingChannelAdapter(1234);
        unicastReceivingChannelAdapter.setOutputChannel(outputChannel());
        return unicastReceivingChannelAdapter;
    }
    
  // Integration 流程

  // 1. 接收從outputChannel出來的資料

  // 2. 將資料格式轉換為字串

  // 3. 格式轉換完後,將資料帶入一個MessageHandler(這邊就是剛剛第一個設定的Bean)

    @Bean
    public IntegrationFlow integrationFlow() {
        return IntegrationFlows
                .from(udpInboundAdapter())
                .transform(Transformers.objectToString())
                .handle(udpMessageHandler())
                .get();
    }
    
}

MessageHandler

MessageHandler將會接收某一個Channel所送來的資料,我們可以接處理訊息的邏輯寫在此。

public class UdpMessageHandler implements MessageHandler {

    private static Logger logger = LogManager.getLogger(UdpMessageHandler.class);
    
    @Override
    public void handleMessage(Message<?> message) throws MessagingException {     
        logger.info(message.getPayload());
    }
    
}

Socket

將該Client跑起來後,對Console進行輸入,Enter後即可輸出資料流。

public class UdpClient {

    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        
        @SuppressWarnings("resource")
        DatagramSocket client = new DatagramSocket();
        InetAddress inetAddress = InetAddress.getByName("localhost");
        
        String sentence = in.readLine();
        byte[] sendData = sentence.getBytes();
        
        DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, inetAddress, 1234);
        client.send(sendPacket);
    }
    
}

App

這邊就是我們的Server端

public class UdpApp {

    public static void main(String[] args) {
        @SuppressWarnings("resource")
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(UdpIntegrationConfig.class);
        context.start();
    }
  
}

Result

直接上圖,上面是Client端,反之下麵則是我們的Sever,我傳送一句Hello過去,可以看到Integration Server即收到該字串。

Refenrence

WIKI - 企業應用整合
EAI技術和概念解析

 
3 months 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

 
3 months 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

 
5 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的時間期限延長。

 
5 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);
}