Home Blog Page 93

Linux – Setup môi trường cho Web Developer

Linux - Setup môi trường cho Web Developer

Bài viết được sự cho phép của tác giả Trần Khôi Nguyên Hoàng

Mình xài Linux cũng một thời gian kha khá và có một số kinh nghiệm trong việc setup môi trường cho việc code Web ở Linux. Cho nên mình xin chia sẻ cho các bạn một số ứng dụng mình cài lên máy phục vụ cho công cụ tạo bug của mình nhé.

Base System

Mình hiện tại đang sử dụng Ubuntu 18.04 làm OS chính của mình. Trước đây có sử dụng Linux Mint 19.2 Tina nhưng cài Docker trên đó hơi có vấn đề một tí (Ít nhất là tới thời điểm viết bài này) nên mình chuyển qua lại Ubuntu cho thân thuộc. Ubuntu mình cài đặt ở chế độ Minimal Installation nhé. Nó sẽ loại bỏ các ứng dụng không cần thiết cho công việc của mình.

Internet Browser & Internet

  • Firefox – Trình duyệt chính của mình trên Linux. Firefox hoạt động trên Linux ổn định và nhẹ nhàng hơn Google Chrome nên mình chọn thằng này.
  • Google Chrome – Tuy vậy, thì Chrome vẫn phải cài đặt để test trên nhiều browser. Mình chỉ thỉnh thoảng mới bật lên để test thôi.
  • Skype – Chat chit một số việc Freelancer với khách hàng trên này.
  • Rambox – Dùng để chat Facebook Messenger là chính. Đỡ phải vào Web.

Tool

  • Shutter – Mặc định thì cái screen shot của Ubuntu khá là cùi. Vì thế nên sử dụng thằng này để screen shot được xịn sò hơn. Nó cũng chỉnh sửa ảnh sau khi screen shot nữa.
  • Draw.io – Máy mình khá yếu nên sử dụng Draw.io trên trình duyệt khá là nặng. Dùng bản Offline của nó ổn hơn nhiều các bạn ạ.
  • Pinta – Cái này dùng để edit ảnh ọt. Nhẹ nhàng đơn giản thôi. Mình không dùng Gimp vì nó nặng nề quá không dùng hết.
  • Typora – Dùng để viết Markdown trực quan. Như ở bài Tại sao nên sử dụng Markdown thay vì Word? thì mình không dùng Office nên dùng thằng này cho nhẹ nhàng.
  • Insomia – Dùng để gọi API thay cho Postman. Mình thích thằng này hơn Postman. Dễ cài đặt hơn, giao diện trực quan dễ nhìn hơn.
  • Ibus-teni: Gõ tiếng việt trên Linux đúng là khổ dâm. Và thằng này, mặc dù giờ ngưng phát triển rồi, nhưng mình thấy ổn trên bản Ubuntu 18.04 này.
  • Synaptic – Cài đặt ứng dụng ngoài, thay cho Ubuntu Software Center.

Editors

  • Visual Studio Code – VS Code thần thánh. Mình code chính Javascript + PHP nên mình dùng thằng này.
  • Atom / Sublime Text – Dùng để mở source code demo là chính.
  • Vim – Tập sử dụng thằng này thay cho nano. Làm màu là chính (nhưng dùng quen thì thấy nó xịn thật).

Terminal

  • Zsh + Oh My Zsh – Dùng bash mặc định của Linux cũng được. Nhưng zsh có nhiều plugin hay hơn nên mình qua thằng này. Mình sử dụng Plugin auto-completation để gõ lệnh nhanh hơn.
  • Cowsay + fortune: Tự động có mấy cái quote hay ho khi bật Terminal lên.
  10 điều bạn có thể làm với Linux mà bạn không thể làm với Windows
  5 lý do lập trình viên nên sử dụng hệ điều hành Linux

Development Environment

  • Docker – Quá nổi tiếng. Không nói nhiều.
  • Docker-compose – Luôn đi kèm với Docker. Cũng không phải nói nhiều.
  • Git – Không nói nói nhiều. Hình như nó được cài mặc định thì phải.

PHP – Javascript

  • LAMP Stack – Stack thay cho XAMPP cho những bạn nào mới nhảy từ Window sang khi code PHP
  • Composer – Mình hay dùng Laravel khi code PHP nên phải install thêm thằng này nữa.
  • Virtualhost – Tạo nhanh Vhost trên Ubuntu. Mình lười tạo cái này mỗi khi có Project mới lắm.
  • NodeJS + NPM – Mình code cả NodeJS nên cài luôn vào máy.

NPM Packages:

Đây là những packages mình cài ở dạng global khi sử code trên NodeJS.

  • live-server – Dùng để tạo Server khi chạy các file HTML/CSS. Các bạn đừng click mở trực tiếp mà nhớ sử dụng thằng này nhé.
  • nodemon – Auto restart lại server khi code NodeJS.
  • localtunnel – Dùng để tạo một đường dẫn thực sự từ local để test. Thường thì mình dùng cái này để test Facebook Messenger Bot.

Kết luận

Trên đây là một số ứng dụng mình dùng để setup cho môi trường làm việc của mình trên Linux của mình. Bài viết chỉ có tính chất tham khảo thôi các bạn nhé các bạn trẻ.

[Tâm sự lâp trình – Phần 2] : Học đại học con đường trở thành lập trình viên

Học đại học con đường trở thành lập trình viên

Bài viết được sự cho phép của tác giả Tin Tran

Và thế là cũng đến ngày nhập học, xách balo lên và đi, hành trang là một túi quần áo cùng với những bỡ ngỡ, ngu ngơ, khờ dại của tuổi trẻ. Cả thế giới công nghệ thông tin dường như đang rộng mở chào đón tôi.

Hôm nay tôi đi học. Trường mới bạn mới và tất cả đều mới đối với tôi, chỉ có cái không mới đó là cái máy tính bàn theo tôi hết năm nhất đại học.

Cũng như các trường đại học khác thì năm đầu tiên đều học các môn đại cương và trường tôi cũng không ngoại lệ. Tôi phải đối mặt với các môn toán cao cấp A1, A2, A3, tin học đại cương, đường lối, Mác lê-nin…vv

  3 Xu hướng tuyển dụng đáp ứng thời đại kỹ thuật số
  Có còn nên học Công nghệ thông tin thời điểm hiện tại?

Ở ngôi trường này tôi đã quen một số bạn mới, những người này đã giúp rất nhiều trong con đường trở thành lập trình viên sau này. Năm nhất thì chưa học lập trình gì cả, chưa có ai có laptop. Sau khi tan học về thì rủ nhau đi quán nước chém gió, đánh bida. Hết năm nhất cũng không có gì đặc biệt.

Năm nhất thì không có gì để nói cả, vì thời gian đó chỉ học những môn đại cương. Từ năm thứ 2 trở đi tôi đã biết tới ngôn ngữ lập trình Java. Java là một ngôn ngữ lập trình mạnh mẽ, đến bây giờ khi đi làm thì tôi mới hồi tưởng được cái sự mạnh mẽ của nó, lần đầu tiên tôi đã được nghe về cái tên lập trình hướng đối tượng. Thực sự mà nói thì lần đầu học nó thì tôi chẳng hiểu gì cả.

Môn đầu tiên học về Java là môn lập trình cơ bản, môn này chủ yếu học về thế nào là hướng đối tượng, class, các lệnh vòng lặp, kiểm tra điều kiên, toán tử, kiểu dữ liệu. Cách biến một đối tượng ở bên ngoài thành một class mà để quản lý dễ dàng hơn. Tôi học môn này cũng dạng bình thường không quá xuất sắc lắm. Trường này dạy chủ yếu là Java nên từ năm 2 đến năm 4 lúc nào cũng thấy Java cả.

Năm học thứ 2 này thì cũng chưa có đồ án lớn nên không có phân chia nhóm, mỗi người tự làm bài tập lớn và nộp cho giáo viên. Và nhóm bạn của tôi chơi thì rất là đoàn kết, một đứa làm và nhiều đứa chép. Dĩ nhiên là cũng có sửa lại đôi chút để thầy cô không biết là chép bài của nhau, đó là suy nghĩ sai lầm của một sinh viên năm 2, tuy nhiên sau khi đi làm thì tôi đã hiểu ra được là không bao giờ lừa dối được thầy cô. Sau khi đi làm thì tôi biết được rằng có tool để hỗ trợ compare(so sánh) giữa các code với nhau, so sánh từ folder đến file, cái nào khác nhau thì nó sẽ show ra hết. Còn tool đó là gì thì mình sẽ giới thiệu ở trong bài viết thủ thuật về lập trình nhé.

Thời gian thấm thoát thoi đưa, tôi đã kết thúc năm 2 và bước vào năm 3 của sinh viên. Cái mốc sinh viên năm thứ 3 là năm của rất nhiều suy nghĩ, rất mông lung, kiến thức rất chi là rời rạc, không xác định được hướng đi cho mình sau này và tôi cũng như thế, cũng chỉ biết đi học và suy nghĩ về tương lai mù mịt của mình.

Tại thời điểm này tôi được học môn mới gọi là lập trình web, dĩ nhiên là cũng học về phân tích thiết kế, cơ sở dữ liệu, cấu trúc dữ liệu và giải thuật. Nhưng ở đây tôi sẽ nói về hướng đi sau này, java thì sẽ có hai hướng đi chính là lập trình web và lập trình mobile. Nhưng trong học kỳ này thì đang học môn lập trình web, đồ án được đưa ra là xây dựng một trang web viết bằng java(jsp/servlet), thầy yêu cầu chia nhóm gồm ba người để cùng làm . Một tuần trôi qua, hai tuần trôi qua, ba tuần trôi qua và chúng tôi vẫn dậm chân tại chỗ, không thằng nào chịu làm cả. Tính tới thời điểm cách ngày báo cáo đồ án là một tuần thì chúng tôi mới bàn nhau về vấn đề làm đồ án, sau khi thảo luận thì quyết định clone giao diện của trang tiki để làm trang web bán sách cho nhóm. Hồi đó trang tiki cũng bình thường, tính ra giao diện cũng dễ nhìn. Rồi chia nhiệm vụ ra, một thằng nghiên cứu viết giao diện cho trang chủ, thằng thì dựng database, kết nối database, thằng thì code xử lý logic.

Sau bốn ngày vật vã, đêm quên ăn ngày quên ngủ thì team đã hoàn thành các chức năng của một web bán sách bao gồm từ trang mua sách của người dùng, các chức năng hiển thị sản phẩm, phân loại sản phẩm, giỏ hàng, thanh toán và trang admin. Vì làm nhanh quá nên mọi người đi báo cáo với một tinh thần hết sức thoải mái và không ai nghĩ sẽ được điểm cao, sau khi báo cáo hoàn tất thầy nhận xét và cho điểm thì OMG. Nhóm chúng tôi nhận được con điểm 9, cả nhóm vỡ òa cảm xúc, rơi nước mắt đầm đìa. Nói đùa cho vui chứ không đến nỗi rơi nước mắt.

Và năm 3 kết thúc trong niềm vui của con điểm 9, đến đây tôi cũng chưa xác định được hướng đi sau này cho bản thân và vẫn mông lung như ngày nào. Hè năm 3 tôi đi làm thêm, được bao ăn và nhận được 2tr trong vòng 2 tháng. Bố mẹ tôi vẫn gửi tiền ăn hàng tháng cho tôi. Tổng lại thì dư được hơn 4tr, tôi quyết định đổi điện thoại mới, trước đó thì tôi đang sử dụng con nokia express music, con này nghe nhạc thì bao hay nhưng tiếc rằng những năm đó là thời đại của smart phone, nên tôi đã mua một con điện thoại samsung galaxy trend có giá 4tr. Kết thúc kỳ nghỉ hè.

Học kỳ I của năm 4 tôi học lập trình thiết bị di động. Lợi thế của java lúc bấy giờ là lập trình android, tạo ra các game, ứng dụng chạy trên smart phone. Và hiện trong tay của tôi đã có một chiếc điện thoại smart phone mà đã mua ở kỳ nghỉ hè. Tôi miệt mài lập trình ra những ứng dụng trong niềm vui sướng được mở mang kiến thức, được làm ra những ứng dụng chạy trên điện thoại. Và tôi đã xác định theo con đường lập trình mobile.

Nhưng đời không như mơ và người tính không bằng trời tính, sang học kỳ II của năm tư vì không đủ điều kiện làm luận văn mà tôi đã chọn học ba môn để ra trường, một trong số đó là môn chuyên đề web. Bước sang học kỳ II tôi chỉ tập trung vào học những cái để làm web và đã lãng quên đi lập trình mobile cái mà tôi đã xác định theo đuổi.

Môn chuyên đề web mà tôi đã nhắc đến sẽ được học về framework để xây dựng web một cách nhanh chóng (lúc này tôi cũng không biết rằng các công ty họ thường dùng framework để xây dựng web). Trên trường thầy dạy một framework này, nhưng thầy bắt mình chọn một framework khác để xây dựng website. Lúc đó học sinh nào cũng chửi thầy là thầy dạy một đường bắt học sinh làm một nẻo. Đến bây giờ khi nhìn lại những ngày tháng đó mới hiểu được tại sao thầy lại làm như vậy. Thầy muốn chúng ta biết thêm được nhiều kiến thức, rèn luyện được cách tiếp cận một công nghê, một framework mới và những cái này đều rất cần thiết khi chúng ta đi làm.

Cũng đã đến lúc chọn framework để làm đồ án, team chúng tôi gồm ba người, sau khi thống nhất thì chúng tôi chọn một framework để làm, do học chuyên về java nên chọn framework được phát triển dựa trên java để làm web và chúng tôi đã chọn ZK framework. Lúc đó chúng tôi chưa biết gì về cách hoạt động và cách làm cũng không một ai biết. Sau một tháng nghiên cứu thì cũng đã hoàn thành được đồ án. Ngày lên thớt cũng đã tới, cả team đã chuẩn bị sẵn sàng giáp lửa, giáp gai, phản dame các kiểu, vì thầy được mệnh danh là người khó tính nhất khoa. Sau ba mươi phút trình bày và giải đáp thắc mắc của thầy thì thầy phán một câu : “Giờ các em muốn mấy điểm?”.

Lúc đó tôi là người trình bày và cũng là người đứng ra nói với thầy như thế này : “Để xứng đáng với công sức của nhóm em thì 8 điểm là số điểm nhóm em muốn”. Và thế là thầy cho nhóm chúng tôi 8 điểm.

Các bạn thấy đó, đi học điểm cao thì không khó, quan trọng là mình chịu làm và chịu học, có nhiều bạn lúc học thì không chịu học, đến lúc làm đồ án thì đi thuê người khác code, đến ngày báo cáo thầy cô hỏi lại thì không biết gì, lúc đó tiền mất, điểm lại không có. Nhóm chúng tôi là tự nghiên cứu, tự thân vận động nên những cái thầy hỏi thì chúng tôi đều trả lời được, thầy chấm điểm dựa trên công sức của chúng tôi bỏ ra. Như câu trả lời của tôi với thầy, tôi thấy tôi đã bỏ ra quá nhiều công sức nên mới dám trả lời như vậy. Các bạn nhớ nhé.

Ở trên tôi có nói là học ba môn, nhưng tôi chỉ đề cập tới môn mà sau này ảnh hưởng đến cuộc đời của tôi, còn những môn khác thì sẽ không nói tới.
Sau khi báo cáo thành công tất cả các môn thì tôi đủ điều kiện để ra trường, ra trường đúng hạn. Một phần do may mắn, một phần là do công sức tôi bỏ ra.

Học đại học tuy dài, nhưng tôi đã kể hết những trải nghiệm của mình chỉ trên dưới 2000 từ , đến đây chắc các bạn đã biết con đường mình đi là con đường nào rồi, đó chính là Lập trình Web. Nó đã theo mình tới tận bây giờ.

Sau những gì tôi đã kể cho mọi người nghe thì tôi rút ra được vài điều sau khi học đại học giành cho các bạn.

1. Học đại học nên có một team cùng học, cùng chơi, để cùng làm đồ án.
2. Hãy tự thân vận động, không ỷ lại vào người khác.
3. Hãy lựa chọn con đường đi cho mình.

Và câu chuyện học đại học đã tới hồi kết, cám ơn các bạn đã quan tâm theo dõi, phần tiếp theo thì tôi sẽ kể về sau khi ra trường, đi xin việc, thử việc nhé.

Bài viết gốc được đăng tải tại chickencodes.com

Có thể bạn quan tâm:

Tìm việc IT lương cao, đãi ngộ tốt trên TopDev

Gửi private message với @SendToUser annotation trong Spring WebSocket

Gửi private message với @SendToUser annotation trong Spring WebSocket

Bài viết được sự cho phép của tác giả Nguyễn Hữu Khanh

Ở bài viết hướng dẫn các bạn cách hiện thực WebSocket với Spring WebSocket, ứng dụng ví dụ của mình sẽ cho phép tất cả các client nhận được tất cả các message khi chúng subscribe vào endpoint, còn ở bài viết hướng dẫn gửi message tới một user cụ thể nào đó, ứng dụng ví dụ của mình cho phép client có thể gửi một message tới một user gắn liền với một sessionId cụ thể. Điểm hơi bất tiện ở đây nếu nhu cầu của chúng ta chỉ là gửi một request tới WebSocket server, cần nó xử lý business logic và trả về kết quả cho chính chúng ta mà thôi, tất nhiên là các bạn cũng có thể giải quyết bài toán này bằng cách gửi STOMP message như mình đã hướng dẫn, nhưng chúng ta cũng phải gán thêm user gắn liền với sessionId để có thể làm được điều này. Sử dụng @SendToUser annotation của Spring WebSocket các bạn sẽ không cần gán thêm thông tin user này. Cụ thể như thế nào? Mình sẽ hướng dẫn các bạn trong bài viết này các bạn nhé.

  Discord đã lưu trữ hàng tỉ messages mỗi ngày như thế nào
  Giới thiệu JMS – Java Message Services

Mình cũng tạo mới một Spring Boot với Web và WebSocket dependency.

Kết quả như sau:

Gửi private message với @SendToUser annotation trong Spring WebSocket

Mình cũng khai báo để sử dụng WebJars với JQuerySocketJS client và Stomp WebSocket dependencies như sau:

<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator-core</artifactId>
</dependency>
<dependency>
<groupId>org.webjars.npm</groupId>
<artifactId>jquery</artifactId>
<version>3.6.0</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>sockjs-client</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>stomp-websocket</artifactId>
<version>2.3.4</version>
</dependency>

Class WebSocketConfiguration để cấu hình WebSocket:

package com.huongdanjava.springboot.websocket;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/hello").withSockJS();
}

@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("queue");
registry.setApplicationDestinationPrefixes("/app");
}
}

Chúng ta sẽ sử dụng annotation @SendToUser trong controller handle request message từ phía client như sau:

package com.huongdanjava.springboot.websocket;

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.stereotype.Controller;

@Controller
public class MessageController {

@MessageMapping("/hello")
@SendToUser("/queue/reply")
public String send(String username) {
return "Hello, " + username;
}
}

Giá trị được khai báo trong annotation @SendToUser chính là endpoint mà client sẽ subcribe để nhận được message từ WebSocket server nha các bạn!

Về bản chất thì Spring cũng sử dụng endpoint gửi message tới một user nào đó với prefix mặc định là “/user”. Các bạn cũng có thể thay đổi prefix mặc định này bằng cách sử dụng phương thức setUserDestinationPrefix() của đối tượng MessageBrokerRegistry trong class WebSocketConfiguration như mình đã làm trong bài viết trước. Điểm khác biệt ở đây là thay vì chúng ta cần phải quản lý user gắn liền với sessionId của WebSocket connection, bây giờ Spring sẽ sử dụng sessionId để chỉ gửi message cho riêng client đang request mà thôi!

Bây giờ mình sẽ thêm code phần front-end thử xem ví dụ của chúng ta hoạt động như thế nào!

index.html:

<!DOCTYPE html>
<html>
<head>
<title>Hello WebSocket</title>
<script src="/webjars/jquery/dist/jquery.min.js"></script>
<script src="/webjars/sockjs-client/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/stomp.min.js"></script>
<script src="/app.js"></script>
</head>
<body>
<div id="main-content">
<div>
<form>
<div>
<label>What is your name?</label> 
<input type="text" id="name" placeholder="Your name here...">
</div>
<button id="send" type="submit">Send</button>
</form>
</div>
</div>
<div>
<label>Message from server: </label><span id="message"></span>
</div>
</body>
</html>

app.js

var stompClient = null;

$(document).ready(function() {
connect();
});

function connect() {
var socket = new SockJS('/hello');
stompClient = Stomp.over(socket);
stompClient.connect({}, function() {
console.log('Web Socket is connected');
stompClient.subscribe('/user/queue/reply', function(message) {
$("#message").text(message.body);
});
});
}

$(function() {
$("form").on('submit', function(e) {
e.preventDefault();
});
$("#send").click(function() {
stompClient.send("/app/hello", {}, $("#name").val());
});
});

Như các bạn thấy, ở phía client, chúng ta sẽ subscribe vào endpoint với prefix mặc định bắt đầu là “/user” như bài viết trước.

Bây giờ, nếu mình sẽ mở 2 cửa sổ trình duyệt, nhập tên mình vào ô “What is your name”, nhấn nút Send, các bạn sẽ thấy chỉ cửa sổ mình đang làm việc nhận được message từ WebSocket server:

Nạp chồng toán tử trong C++

Nạp chồng toán tử trong C++

Bài viết được sự cho phép của tác giả Khiêm Lê

Nếu bạn đã học qua lập trình C++ cơ bản, chắc chắc rằng trong hầu hết các bài tập về C++, bạn đều sử dụng các toán tử số học như cộng, trừ, nhân, chia. Hầu hết các toán tử đó đều được thực hiện trên toán hạng có kiểu dữ liệu cơ bản như int, float, double…

int a = 5;
int b = 4;
int c = a + b;  // = 9

Vậy nếu như bạn muốn thực hiện các toán tử đó đối với toán hạng có kiểu dữ liệu bạn tự định nghĩa thì làm sao?

PhanSo ps1(1, 2);
PhanSo ps2(2, 3);
// Làm sao để có thể cộng hai phân số?
PhanSo ketQua = ps1 + ps2; 

Đây chính là lúc chúng ta sử dụng nạp chồng toán tử. Vậy hãy cùng tìm hiểu xem nạp chồng toán tử là gì và cách nạp chồng toán tử như thế nào.

Nạp chồng toán tử là gì?

Cũng tương tự như nạp chồng hàm (overload function), bạn có thể định nghĩa nhiều hàm có cùng tên, nhưng khác tham số truyền vào, nạp chồng toán tử cũng tương tự.

Nạp chồng toán tử (overload operator) là bạn định nghĩa lại toán tử đã có trên kiểu dữ liệu người dùng tự định nghĩa để dể dàng thể hiện các câu lệnh trong chương trình.

Ví dụ như bạn định nghĩa phép cộng cho kiểu dữ liệu phân số thì sẽ thực hiện cộng hai phân số rồi trả về một phân số mới. So với việc thực hiện gọi hàm, việc overload toán tử sẽ làm cho câu lệnh ngắn gọn, dễ hiểu hơn.

PhanSo ps1(1, 2);
PhanSo ps2(2, 3);
PhanSo ketQua;
// Dùng hàm
ketQua = ps1.cong(ps2);
// Dùng Overload operator
ketQua = ps1 + ps2;  // 7/6

Cơ chế hoạt động

Về bản chất, việc thực hiện các toán tử cũng tương đương với việc gọi hàm, ví dụ:

PhanSo a(1, 2);
PhanSo b(2, 3);
PhanSo ketQua = a + b;
// Tương đương với
PhanSo ketQua = a.cong(b);

Nếu bạn thực hiện toán tử trên hai toán hạng có kiểu dữ liệu cơ bản (float, double, int…), trình biên dịch sẽ tìm xem phiên bản nạp chồng toán tử nào phù hợp với kiểu dữ liệu đó và sử dụng, nếu không có sẽ báo lỗi.

Ngược lại nếu là kiểu dữ liệu tự định nghĩa như struct, class, trình biên dịch sẽ tìm xem có phiên bản nạp chồng toán tử nào phù hợp không? Nếu có thì sẽ sử dụng toán tử đó, ngược lại thì sẽ cố gắng chuyển đổi kiểu dữ liệu của các toán hạng đó sang kiểu dữ liệu có sẵn để thực hiện phép toán, không được sẽ báo lỗi.

Các toán tử có thể được overload

+ * / % ^ & |
~ ! = < > += -= *=
/= %= ^= &= |= << >> >>=
<<= == != <= >= && || ++
->* , -> [] () new delete
new[] delete[]

Vậy ta chỉ có một số toán tử sau không overload được:

  • Toán tử . (chấm)
  • Toán tử phạm vi ::
  • Toán tử điều kiện ?:
  • Toán tử sizeof

Một số lưu ý:

  • Các toán tử một ngôi (–, ++) có thể đứng trước hoặc sau toán hạng
  • Một số toán tử có thể làm toán tử một ngôi hoặc hai ngôi như toán tử *
  • Toán tử chỉ mục ([…]) là toán tử hai ngôi
  • Các từ khóa new và delete cũng được xem là toán tử nên có thể được overload

Cú pháp overload

Như đã giới thiệu, bản chất việc dùng toán tử là lời gọi hàm, do đó chúng ta overload toán tử cũng giống overload hàm, vậy chúng ta sẽ overload hàm nào? Chúng ta sẽ overload hàm có tên là “operator@”, với @ là toán tử cần overload (+, -, *, /…), kiểu trả về của hàm chính là lớp đó.

Có hai loại là hàm cục bộ (dùng phương thức của lớp) và hàm toàn cục (dùng hàm bạn). Chúng ta sẽ lần lượt tìm hiểu cách overload toán tử bằng cả hai cách.

Cài đặt với hàm cục bộ

Đối với hàm cục bộ hay còn gọi là phương thức của lớp, số tham số sẽ ít hơn hàm toàn cục một tham số vì tham số đầu tiên mặc định chính là toán hạng đầu tiên. Vậy, đối với toán tử hai ngôi, ta chỉ cần truyền một tham số cho hàm, chính là toán hạng thứ hai. Ví dụ:

class PhanSo
{
    int tu;
    int mau;

public:
    PhanSo() : tu(0), mau(1) {}

    PhanSo operator+(const PhanSo &ps)    // overload toán tử +
    {
        PhanSo kq;
        kq.tu = this->tu * ps.mau + ps.tu * this->mau;
        kq.mau = this->mau * ps.mau;
        return kq;
    }
};

Sau khi overload toán tử, bạn có thể sử dụng nó trên kiểu dữ liệu bạn đã định nghĩa:

    PhanSo ps1(1, 2);
    PhanSo ps2(2, 3);
    PhanSo ps3 = ps1 + ps2;  // = 1/2 + 2/3

Giờ chúng ta hãy xem một ví dụ khác, overload toán tử cộng một phân số với một số nguyên.

class PhanSo
{
    // properties & methods
    PhanSo operator+(const int &i)
    {
        PhanSo kq;
        kq.tu = this->tu + i * this->mau;
        return kq;
    }
}

// Sử dụng
PhanSo ps1(1, 2);
PhanSo ps2 = ps1 + 2;   // = 5/2

Do toán tử overload theo cách này là phương thức, được gọi từ một đối tượng, do đó mặc định toán hạng đầu tiên phải là toán hạng có kiểu dữ liệu của lớp đó, điều này cũng có nghĩa là bạn phải đặt toán hạng có kiểu dữ liệu của lớp đó đầu tiên rối mới đến toán hạng tiếp theo. Và đối với các kiểu dữ liệu có sẵn, ta không thể truy cập vào các lớp định nghĩa nên chúng, do đó ta không thể overload operator của chúng được. Vậy để giải quyết điều này thì làm như thế nào? Ta sẽ sử dụng hàm toàn cục.

Cài đặt với hàm toàn cục

Thay vì đối với việc toán hạng đầu tiên luôn phải có kiểu là lớp nào đó, chúng ta sẽ sử dụng hàm bạn để có thể tự do lựa chọn thứ tự của các toán hạng. Ví dụ như bạn muốn 1 + ps1, ps1 + 1 đều được chứ không nhất thiết phải là ps1 + 1 nữa. Chúng ta cài đặt với hàm bạn tương tự như sau:

class PhanSo
{
    // properties & methods
    friend PhanSo operator+(const PhanSo &ps, const int &i); 
}

PhanSo operator+(const PhanSo &ps, const int &i)
{
    PhanSo kq;
    kq.tu = ps.tu + i * ps.mau;
    return kq;
}

// Sử dụng
PhanSo ps1(1, 2);
PhanSo ps2 = ps1 + 2;   // = 5/2

Khá là giống với overload toán tử theo cách dùng phương thức phải không? Vậy nếu muốn đổi thứ tự toán hạng thì phải làm sao, đơn giản đổi thứ tự tham số là được:

PhanSo operator+(const int &i, const PhanSo &ps)  // hàm bạn của class PhanSo
{
    return ps + i;
}

Chuyển kiểu

Có hai loại chuyển kiểu là chuyển kiểu bằng toán tử chuyển kiểu và chuyển kiểu bằng constructor

Overload toán tử chuyển kiểu

Như đã trình bày trong phần cơ chế, nếu không tìm thấy phiên bản nạp chồng toán tử nào phù hợp với kiểu dữ liệu của toán hạng, trình biên dịch sẽ chuyển đối toán hạng sang kiểu dữ liệu cơ bản để tính toán. Vậy chúng ta sẽ cần overload toán tử chuyển kiểu để trình biên dịch có thể chuyển kiểu dữ liệu ta định nghĩa sang kiểu dữ liệu cơ bản.

Ví dụ như mình muốn chuyển phân số về số thực, mình sẽ overload toán tử chuyển kiểu float:

// Cú pháp
operator T()
{
    return x; // x có kiểu dữ liệu là T
}

// Ví dụ overload toán tử chuyển kiểu float
class PhanSo
{
    // properties & methods
    operator float();
}

PhanSo::operator float()
{
    return (float)this->tu / this->mau;
}

Lúc này, ta sẽ có thể thực hiện chuyển kiểu dữ liệu:

PhanSo ps1(1, 2);
PhanSo ps2(2, 3);
float a = ps1 + ps2;
cout << a  << endl;    // ~ 1.67
cout << (float)ps1;   // = 0.5

Chuyển kiểu bằng constructor

Để hạn chế việc phải overload toán tử với các toán hạng có kiểu dữ liệu khác nhau, ta sử dụng chuyển kiểu bằng constructor. Ví dụ:

// constructor
PhanSo(int t, int m = 1) : tu(t), mau(m) {}

Với constructor được khai báo như trên, khi ta thực hiện cộng một số nguyên với một kiểu phân số, số nguyên sẽ được trình biên dịch chuyển thành kiểu phân số thông qua việc gọi constructor bên trên, với mẫu số là 1 và tử chính là toán hạng ta đang cộng.

PhanSo ps(1, 2);
PhanSo kq = ps + 3; // = 7/2
// Có thể hiểu là
PhanSo kq = ps + PhanSo(3);

Sự nhập nhằng

Sự nhập nhằng xảy ra khi bạn thực hiện chuyển kiểu bằng constructor và chuyển kiểu bằng toán tử chuyển kiểu. Sự nhập nhằng khiến cho trình biên dịch không xác định được nên chuyển kiểu bằng toán tử chuyển kiểu hay constructor, dẫn đến việc mất đi cơ chế chuyển kiểu tự động (ngầm định).

class PhanSo
{
    // properties & methods
    PhanSo(int t, int m = 1) : tu(t), mau(m) {}
    operator double();
}

PhanSo::operator double()
{
    return (double)this->tu / this->mau;
}

PhanSo a(2, 3), b(3, 4), c;
c = a + b;
c = 2 + a;  // lỗi do sự nhập nhằng
c = a + 2.5;  // lỗi do sự nhập nhằng

Cách xử lý duy nhất cho việc này là thực hiển chuyển kiểu tường minh, việc này làm mất đi sự tiện lợi của cơ chế chuyển kiểu tự động. Do đó khi thực hiện chuyển kiểu, ta phải hi sinh một trong hai, hoặc là chuyển kiểu bằng constructor, hoặc là overload toán tử chuyển kiểu.

Các phép toán đã có, toán tử chuyển kiểu cũng đã có, vậy bây giờ nhập hay xuất phân số ta vẫn phải tự nhập xuất tử và mẫu à? Chúng ta sẽ overload luôn toán tử >> và <<, hãy xem thực hiện như thế nào.

Overload toán tử nhập xuất

Một toán tử nhập xuất sẽ có hai toán hạng, bến trái là istream hoặc ostream, bên phải là toán hạng cần nhập, xuất. Để overload, chúng ta sử dụng hàm toàn cục, có hai tham số, tham số đầu tiên là một tham chiếu đến đối tượng kiểu istream hoặc ostream, tham số thứ hai là một tham chiếu đối tượng cần nhập, xuất, kiểu trả về của hàm chính là tham chiếu đến tham số đầu tiên của hàm (istream hoặc ostream).

Toán tử nhập

Mình sẽ thực hiện overload toán tử nhập cho lớp phân số của mình như sau:

class PhanSo
{
    // properties & methods
    friend istream &operator>>(istream &in, PhanSo &ps);
}

istream &operator>>(istream &in, PhanSo &ps)
{
    cout << "Tu: ";
    in >> ps.tu;
    cout << "Mau: ";
    in >> ps.mau;
    return in;
}

Như vậy toán tử nhập đã được overload cho lớp phân số, bây giờ khi gọi toán tử nhập chúng ta sẽ được kết quả sau:

PhanSo ps;
cin >> ps;
// Tu: 1
// Mau: 2

Toán tử xuất

Đối với toán tử nhập cũng tương tự, chúng ta cũng thực hiện tương tự như sau:

class PhanSo
{
    // properties & methods
    friend ostream &operator<<(ostream &out, const PhanSo &ps);
}

ostream &operator<<(ostream &out, const PhanSo &ps)
{
    if (ps.tu == 0)
        out << 0;
    else if (ps.mau == 1)
        out << ps.tu;
    else
        out << ps.tu << '/' << ps.mau;
    return out;
}

Bây giờ bạn có thể sử dụng toán tử xuất bình thường như các kiểu dữ liệu cơ bản khác:

PhanSo ps(1, 2);
cout << ps; // 1/2

Hạn chế của việc overload toán tử

  • Không thể tạo toán tử mới
  • Không thể kết hợp các toán tử theo cách mà trước đó không được định nghĩa
  • Không thay đổi được thứ tự ưu tiên toán tử
  • Không thể tạo cú pháp mới cho toán tử
  • Không thể định nghĩa lại một định nghĩa đã có của một toán tử

Một số ràng buộc của toán tử

  • Hầu hết các toán tử không ràng buộc ý nghĩa, ngoại trừ một số toán tử =, [], (), -> thì phải được định nghĩa là hàm thành phần của lớp để toán hạng đầu tiên luôn nằm bên trái
  • Nếu đã overload toán tử rồi thì phải làm cho đầy đủ. Ví dụ overload +, -, *, / thì phải overload luôn cả +=, -=, *=, /=…
  • Luôn tôn trọng ý nghĩa của toán tử gốc (+ thì phải cộng, – phải trừ…)
  • Cố gắng tái sử dụng mã nguồn một cách tối đa (ví dụ như đảo thứ tự toán hạng như ví dụ ở trên)

Tổng kết

Vậy là trong bài viết này, mình đã giới thiệu cho các bạn cách nạp chồng toán tử trong C++. Nếu bạn thấy hay, hãy chia sẻ cho bạn bè cùng biết, và đừng ngần ngại góp ý dưới bài viết để giúp mình phát triển bài viết tốt hơn. Cảm ơn các bạn đã theo dõi bài viết.

Bài viết gốc được đăng tải tại khiemle.dev

Có thể bạn quan tâm:

Xem thêm Việc làm Developer hấp dẫn trên TopDev

Hàm Python tích hợp sẵn

Hàm Python tích hợp sẵn

Bài viết được sự cho phép của tác giả Nguyễn Chí Thức

Python có hai loại hàm cơ bản, một là hàm tích hợp sẵn, hai là hàm do người dùng tự định nghĩa. Hôm nay, chúng ta sẽ tìm hiểu về danh sách các hàm được tích hợp sẵn trên Python.

Trình thông dịch của Python có sẵn một số hàm để sử dụng. Các hàm này được gọi là hàm tích hợp. Ví dụ, print() là hàm in các giá trị được cung cấp cho hàm ra màn hình, hàm list() tạo một list trong Python.

Trong phiên bản Python 3.6 có 68 hàm Python được tích hợp sẵn. Dưới đây là danh sách các hàm đó, cùng với mô tả ngắn gọn về chúng. Bạn có thể bookmark lại để tra nhanh khi cần nhé

Tìm việc làm lập trình python mới nhất trên TopDev

Nhấn Ctrl+F trên trình duyệt và nhập tên hàm cần tìm, nếu bạn muốn tìm nhanh nhé.

Hàm Mô tả
abs() Trả về giá trị tuyệt đối của một số
all() Trả về True khi tất cả các phần tử trong iterable là đúng
any() Kiểm tra bất kỳ phần tử nào của iterable là True
ascii() Tả về string chứa đại diện (representation) có thể in
bin() Chuyển đổi số nguyên sang chuỗi nhị phân
bool() Chuyển một giá trị sang Boolean
bytearray() Trả về mảng kích thước byte được cấp
bytes() Trả về đối tượng byte không đổi
callable() Kiểm tra xem đối tượng có thể gọi hay không
chr() Trả về một ký tự (một chuỗi) từ Integer
classmethod() Trả về một class method cho hàm
compile() Trả về đối tượng code Python
complex() Tạo một số phức
delattr() Xóa thuộc tính khỏi đối tượng
dict() Tạo Dictionary
dir() Trả lại thuộc tính của đối tượng
divmod() Trả về một Tuple của Quotient và Remainder
enumerate() Trả về đối tượng kê khai
eval() Chạy code Python trong chương trình
exec() Thực thi chương trình được tạo động
filter() Xây dựng iterator từ các phần tử True
float() Trả về số thập phân từ số, chuỗi
format() Trả về representation được định dạng của giá trị
frozenset() Trả về đối tượng frozenset không thay đổi
getattr() Trả về giá trị thuộc tính được đặt tên của đối tượng
globals() Trả về dictionary của bảng sumbol toàn cục hiện tại
hasattr() Trả về đối tượng dù có thuộc tính được đặt tên hay không
hash() Trả về giá trị hash của đối tượng
help() Gọi Help System được tích hợp sẵn
hex() Chuyển Integer thành Hexadecimal
id() Trả về định danh của đối tượng
input() Đọc và trả về chuỗi trong một dòng
int() Trả về số nguyên từ số hoặc chuỗi
isinstance() Kiểm tra xem đối tượng có là Instance của Class không
issubclass() Kiểm tra xem đối tượng có là Subclass của Class không
iter() Trả về iterator cho đối tượng
len() Trả về độ dài của đối tượng
list() Tạo list trong Python
locals() Trả về dictionary của bảng sumbol cục bộ hiện tại
map() Áp dụng hàm và trả về một list
max() Trả về phần tử lớn nhất
memoryview() Trả về chế độ xem bộ nhớ của đối số
min() Trả về phần tử nhỏ nhất
next() Trích xuất phần tử tiếp theo từ Iterator
object() Tạo một đối tượng không có tính năng (Featureless Object)
oct() Chuyển số nguyên sang bát phân
open() Trả về đối tượng File
ord() Trả về mã Unicode code cho ký tự Unicode
pow() Trả về x mũ y
print() In đối tượng được cung cấp
property() Trả về thuộc tính property
range() Trả về chuỗi số nguyên từ số bắt đầu đến số kết thúc
repr() Trả về representation có thể in của đối tượng
reversed() Trả về iterator đảo ngược của một dãy
round() Làm tròn số thập phân
set() Tạo một set các phần tử mới
setattr() Đặt giá trị cho một thuộc tính của đối tượng
slice() Cắt đối tượng được chỉ định bằng range()
sorted() Trả về list được sắp xếp
staticmethod() Tạo static method từ một hàm
str() Trả về một representation không chính thức của một đối tượng
sum() Thêm một mục vào Iterable
super() Cho phép tham chiếu đến Parent Class bằng super
tuple() Tạo một Tuple
type() Trả về kiểu đối tượng
vars() Trả về thuộc tính __ dict __ của class
zip() Trả về Iterator của Tuple
__ import __() Hàm nâng cao, được gọi bằng import

Nếu muốn biết hàm này cụ thể làm gì, có đối số nào, bạn chỉ cần nhập lệnh:

print(ten_ham.__doc__)

Python sẽ giải thích khá đầy đủ về hàm, bạn có thể đọc và làm vài ví dụ để hiểu hàm đó.

Bài viết gốc được đăng tải tại ucode.vn

  Cấu trúc dữ liệu Set trong Python
  Tham số hàm Python

Xem thêm Việc làm Developer hấp dẫn trên TopDev

10 lý do kiểm thử phần mềm trở thành một nghề thời thượng

10 lý do kiểm thử phần mềm trở thành một nghề thời thượng

Bài viết được sự cho phép của vntesters.com

10. Không có kiểm thử phần mềm – Không có phần mềm.
Nếu như không có kiểm thử, phần mềm trở nên vô dụng. Kiểm thử phần mềm không tạo nên phần mềm mà kiểm thử giúp phần mềm có thể sử dụng được.

9. Bạn giữ được uy tín và tiết kiệm chi phí cho công ty mình (Điểm mấu chốt).
Bạn phát hiện và xử lý các lỗi để khách hàng của bạn không phải giải quyết chúng. Khi khách hàng phải đối mặt với những lỗi, điều này sẽ tổn hao công ty bạn nhiều hơn nữa (chẳng hạn như chi phí, uy tính, thời gian, v.v…).

  04 Điều Cần Chú Ý Cho Người Mới Làm Automation Test
  A/B testing và những tiêu chí chính để đánh giá sự thành công của ASO

8. Đem đến sự yên tâm (Sự tự tin).
Ngày nay, phần mềm là một phần không thể thiếu trong cuộc sống chuyên nghiệp. Vì thế, khi bạn kiểm thử phần mềm tốt, bạn sẽ giúp công ty bạn và khách hàng có sự tự tin hơn vào phần mềm. Bạn cung cấp những thông tin thiết yếu tạo niềm tin của tòan đội vào sản phầm và lợi ích cho mọi người.

7. Bạn làm hài lòng nhiều người (Niềm phấn khởi).
Bạn thỏa mãn được nhu cầu khách hàng về tiêu chí thân thiện với người sử dụng.

6. Kiểm thử phần mềm tạo cho bạn cơ hội liên tục tiếp cận những điều tốt nhất và mới nhất.
Kiểm thử phần mềm đòi hỏi một quá trình học hỏi, rèn luyện và thay đổi không ngừng. Bạn phải liên tục nắm bắt những giải pháp kiểm thử mới để bắt kịp các phương pháp phát triển, những công nghệ nền tảng mới, những sáng kiến sản phẩm mới và những phương án mới cho những sản phẩm phần mềm được ứng dụng.
Kiểm thử phần mềm không chỉ giúp bạn đơn thuần tiếp cận với phần mềm. Kiểm thử còn bao gồm yếu tố phân tích kinh doanh. Để trở thành một kỹ sư kiểm thử giỏi bạn sẽ phải luôn tìm hiểu nhu cầu kinh doanh của khách hàng.

5. Kiểm thử phần mềm đòi hỏi cao về tư duy, phân tích và sáng tạo.
Kiểm thử phần mềm tạo điều kiện cho bạn tận dụng tối đa tư duy đánh giá và sáng tạo để bạn có thể phát hiện ra những điểm mà người khác chưa nhìn thấy.
Kiểm thử phần mềm có thể là một thách thức tư duy thú vị. Kiểm thử phần mềm đòi hỏi kỹ thuật ít hơn so với phát triển phần mềm. Nhưng điều này không có nghĩa kiểm thử là thứ cấp. Bạn không nhất thiết là một nhà lập trình đam mê mới có thể trở thành một kỹ sư kiểm thử phần mềm. Tuy nhiên, bạn phải có những kỹ năng sáng tạo và phân tích tốt để có thể giúp bạn phát triển xa hơn trong nghề.

4. Nhiều người có thể làm, nhưng rất ít người có thể làm tốt
Hiện tại vẫn còn rất ít chuyên gia về lĩnh cực kiểm thử phần mềm. Nói một cách cụ thể, có tương đối ít người thực sự giỏi về lĩnh vực này. Đây là một cơ hội cho bạn.

3. Kiểm thử phần mềm luôn là một nghề cần thiết.
Kiểm thử chiếm từ 30% – 40% trong qui trình phát triển phần mềm, vì thế, luôn luôn có nhu cầu cao. Nhiều người có thể kiểm thử phần mềm, tuy nhiên chỉ có một số rất ít có thể kiểm thử hiệu quả.

2. Kiểm thử phần mềm là một nghề thử thách và xứng đáng.
Trong khi các kỹ sư phát triển phần mềm thường làm việc với một hoặc nhiều khâu của phần mềm, chứ không phải toàn bộ phần mềm, thì các kỹ sư kiểm thử phần mềm có cơ hội làm việc trên sản phẩm từ A đến Z (sản phẩm mang tính hòan chỉnh hơn một phần mềm) và ở cấp độ chi tiết sâu hơn. Kiểm thử phần mềm chuyên nghiệp đòi hỏi sự phối hợp các kỹ năng gồm kiểm thử phần mềm, kỹ thuật công nghệ thông tin, chuyên môn miền (domain), quản lý dự án và kỹ năng truyền thông.
Kiểm thử phần mềm yêu cầu nhiều kỹ năng truyền thông và ngôn ngữ (chẳng hạn tiếng Anh nếu bạn làm việc với một nhóm phát triển của Mỹ). Bạn càng có kỹ năng truyền thông ngôn ngữ tốt, bạn càng được đền bù xứng đáng.
Kiểm thử là cầu nối từ phát triển phần mềm đến các khách hàng, những người sử dụng phần mềm. Kiểm thử hiệu quả là công việc mô hình hóa kinh doanh, phân tích ứng dụng và thấu hiểu thị trường.

1. Kiểm thử phần mềm là một nghề có phúc lợi tốt với cơ hội thăng tiến nghề nghiệp nhanh chóng và đa dạng.
Kiểm thử phần mềm là một ngành công nghiệp có nhiều tiềm năng nghiên cứu, học tập, tiên phong và sáng tạo. Vì vậy, lĩnh vực này có nhiều cơ hội thăng tiến sự nghiệp của bạn một cách nhanh chóng, và được hưởng những phúc lợi xứng đáng, cũng như cơ hội trở thành những nhà lãnh đạo tư duy.
Trong nghề kiểm thử phần mềm có rất nhiều cơ hội phát triển. Bạn có thể trở thành kỹ sư cấp cao, trưởng dự án, nhà quản lý dự án, nhà quản lý cấp cao, giám đốc, v.v…

Nguồn: http://www.logigear.vn

Bài viết gốc được đăng tải tại vntesters.com

Có thể bạn quan tâm:

Xem thêm tuyển dụng it remote hấp dẫn trên TopDev

squash commits lúc merge từ branch của bạn

squash commits lúc merge từ branch của bạ

Bài viết được sự cho phép của tác giả Lê Chí Dũng

Thông thường một feature mới thường được làm trên một nhánh (branch) riêng và thường xuyên pull các cập nhật từ nhánh master trong quá trình đấy.

  Merge vs Rebase trong Git
  Sự khác biệt giữa ‘git merge’ và ‘git rebase’ là gì?

Sau khi branch này đã được kiểm tra và chấp nhận để merge vào thì lệnh git merge được sử dụng để nhập nhánh này. Xin lưu ý khi merge một nhánh vào thì tất cả commit của nhánh đấy cộng với một commit mới (gọi là meta merge commit) sẽ được thêm vào nhánh chỉ định. Ở một số cty tôi đã từng biết thì các feature đều được quản lý bằng các ticket system và để thuận tiện cho việc truy xuất sau này thì tất cả commit của branch feature đc nén (squash) thành một commit trước khi merge vào master. Thủ tục thông thường là dùng git rebase -i tuy thủ tục này khá là mất thời gian nếu bạn có nhiều commit. Có một chiêu khác tiện lợi hơn tôi muốn chia sẻ là sử dụng

git merge --squash

lệnh này sẽ squash tất cả commit lại thành một và hỏi người dùng tên commit mới là gì rồi sẽ merge vào branch. Nếu bạn muốn edit commit message thì có thế dùng

git merge --squash --edit

Mình chưa bàn về việc để nguyên commit history vs nén lại thành 1 là tốt hay dở, vì mỗi cty có cách thức quản lý riêng, nhưng riêng tôi thì tôi thấy cứ để nguyên để giúp người review sau này có đc một cái nhìn toàn diện về progress của branch, vấn đề chính là sử dụng GitHub hiệu quả (chú thích đầy đủ) để giúp người khác sau này có thể quay lại kiểm tra.
Bài viết gốc được đăng tải tại lcdung.top
Có thể bạn quan tâm:
Xem thêm Việc làm Developer hấp dẫn trên TopDev

Hướng dẫn cài đặt Elasticsearch, Logstash và Kibana (ELK Stack) trên CentOS 7 để quản lý Log

Hướng dẫn cài đặt Elasticsearch, Logstash và Kibana (ELK Stack) trên CentOS 7 để quản lý Log

Bài viết được sự cho phép của tác giả Lê Chí Dũng

Lý do bạn áp dụng cài đặt Elasticsearch, Logstash và Kibana (ELK Stack):

  1. Dễ dàng kiểm soát rủi ro phát hiện bug sớm nhất khi vận hành hệ thống nhiều server.
  2. Dễ dàng kiểm tra và xem thông tin log mong muốn.
  3. Dễ dàng phân loại log của server theo ý muốn.
  4. Áp dụng kiểm soát sản phẩm lớn như e-commerce, services,…
  Elasticsearch là gì? Tìm hiểu về Elasticsearch
  Function-Score trong Elasticsearch

1. Giới thiệu công cụ:

Logstash: Đây là một công cụ sử dụng để thu thập, xử lý log được viết bằng java. Nhiệm vụ chính của logstash là thu thập log sau đó chuyển vào Elastichsearch. Mỗi dòng log của logstash được lưu trữ đưới dạng json.

Elasticsearch: sử dụng cơ sở dữ liệu NoSQL dựa trên nền tảng của Apache Lucene engine. Dùng để lưu trữ dữ liệu và cung cấp interface cho phép truy vấn đến cơ sở dữ liệu.

Kibana: Đây là giao diện sử dụng dành cho người dùng trên môi trường web. Kibana sẽ sử dụng Elashtichsearch để tìm kiếm các dữ liệu phù hợp với yêu cầu của người dùng.

2. Chuẩn bị và cài đặt:

1. Chuẩn bị:

Logstash và Elasticsearch yêu cầu Java nên cần có một Java Virtual Machine để hoạt động. Vì vậy trước tiên bạn cần cài đặt Java, nên cài phiên bản mới nhất của java hiện nay Java8. Nếu đã cài đặt Java thì có thể bỏ qua bước này.

Tải file cài đặt Java

cd ~
wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie" "http://download.oracle.com/otn-pub/java/jdk/8u73-b02/jdk-8u73-linux-x64.rpm"

Cài đặt Java

sudo yum -y localinstall jdk-8u73-linux-x64.rpm

Xóa file cài đặt

rm ~/jdk-8u*-linux-x64.rpm

Tạo SSL Certificates

Vì chúng ta sẽ sử dụng Filebeat để ghi từ Server Client vào Server ELK (Elasticsearch, Logstash, Kibana) của mình, nên cần phải tạo ra SSL Certificates và cặp khóa. Các chứng chỉ được sử dụng bởi Filebeat để xác minh danh tính của Server ELK.

Nếu bạn không có thiết lập DNS cho phép các máy chủ của bạn, bạn sẽ thu thập các bản ghi từ, để giải quyết địa chỉ IP của Server ELK bằng cách thêm địa chỉ IP riêng Server đến SubjectAltName (SAN) trường của các chứng chỉ SSL. Để làm như vậy, mở tập tin cấu hình OpenSSL:

sudo vi /etc/pki/tls/openssl.cnf

Cập nhật dòng sau:

# IP của ELK server
subjectAltName = IP: 139.162.3.1

Sau khi lưu lại chạy lệnh sau để tạo ra các chứng chỉ SSL và khóa riêng tại (/etc/PKI/TLS/):

cd /etc/pki/tls
sudo openssl req -config /etc/pki/tls/openssl.cnf -x509 -days 3650 -batch -nodes -newkey rsa:2048 -keyout private/logstash-forwarder.key -out certs/logstash-forwarder.crt

Cài đặt Nginx để chạy giao diện web của Kibana

2. Cài đặt Logstash

2.1 Tạo hoặc cập nhật file Yum repository cho Logstash:

sudo vi /etc/yum.repos.d/logstash.repo

2.2 Lưu nội dung file Yum repository:

[logstash-2.2]
name=logstash repository for 2.2 packages
baseurl=http://packages.elasticsearch.org/logstash/2.2/centos
gpgcheck=1
gpgkey=http://packages.elasticsearch.org/GPG-KEY-elasticsearch
enabled=1

2.3 Cài đặt Logstash với lệnh sau:

sudo yum -y install logstash

2.4 Sau khi cài đặt xong, tiến hành cấu hình Logstash:

File cấu hình Logstash lưu tại /etc/logstash/conf.d theo định dạng JSON.

Cấu hình gồm 3 phần: inputs, filters, and outputs.

2.4.1 Trước tiên, tạo cấu hình input “filebeat” lưu tại 02-beats-input.conf:

sudo vi /etc/logstash/conf.d/02-beats-input.conf

Lưu thông tin cấu hình input không sử dụng SSL:

input {
  beats {
    port => 5044
    ssl => false
    #ssl_certificate => "/etc/pki/tls/certs/logstash-forwarder.crt"
    #ssl_key => "/etc/pki/tls/private/logstash-forwarder.key"
  }
}

Lưu thông tin cấu hình input có sử dụng SSL:

input {
  beats {
    port => 5044
    ssl => true
    ssl_certificate => "/etc/pki/tls/certs/logstash-forwarder.crt"
    ssl_key => "/etc/pki/tls/private/logstash-forwarder.key"
  }
}

2.4.2 Kế tiếp,  tạo cấu hình filter “filebeat” lưu tại 10-syslog-filter.conf:

sudo vi /etc/logstash/conf.d/10-syslog-filter.conf

Lưu thông tin cấu hình filter

filter {
  if [type] == "syslog" {
    grok {
      match => { "message" => "%{SYSLOGTIMESTAMP:syslog_timestamp} %{SYSLOGHOST:syslog_hostname} %{DATA:syslog_program}(?:\[%{POSINT:syslog_pid}\])?: %{GREEDYDATA:syslog_message}" }
      add_field => [ "received_at", "%{@timestamp}" ]
      add_field => [ "received_from", "%{host}" ]
    }
    syslog_pri { }
    date {
      match => [ "syslog_timestamp", "MMM  d HH:mm:ss", "MMM dd HH:mm:ss" ]
    }
  }
}

2.4.3 Cuối cùng,  tạo cấu hình out put “filebeat” lưu tại 30-elasticsearch-output.conf:

sudo vi /etc/logstash/conf.d/30-elasticsearch-output.conf

Lưu thông tin cấu hình out put:

output {
  elasticsearch {
    hosts => ["localhost:9200"]
    sniffing => true
    manage_template => false
    index => "%{[@metadata][beat]}-%{+YYYY.MM.dd}"
    document_type => "%{[@metadata][type]}"
  }
}

Lưu lại và thoát. Theo cấu hình output Logstash để lưu beats data trong Elasticsearch cần  running localhost:9200

Nếu muốn thêm cấu hình  filter thì chắc chắn tên phải nằm giữa input và output (nằm giữa 02- và 30-)

2.5 Chạy lệnh kiểm tra cấu hình service Logstash:

sudo service logstash configtest

Kết quả thành công:

2.6 Restart và enable Logstash:

sudo systemctl restart logstash
sudo chkconfig logstash on

3. Cài đặt Elasticsearch

3.1 Import Elasticsearch public GPG key vào rpm:

sudo rpm --import http://packages.elastic.co/GPG-KEY-elasticsearch

3.2 Tạo hoặc cập nhật file Yum repository cho Elasticsearch:

sudo vi /etc/yum.repos.d/elasticsearch.repo

3.2 Lưu nội dung file Yum repository:

[elasticsearch-2.x]
name=Elasticsearch repository for 2.x packages
baseurl=http://packages.elastic.co/elasticsearch/2.x/centos
gpgcheck=1
gpgkey=http://packages.elastic.co/GPG-KEY-elasticsearch
enabled=1

3.3 Cài đặt Elasticsearch:

sudo yum -y install elasticsearch

3.4 Cấu hình Elasticsearch:

sudo vi /etc/elasticsearch/elasticsearch.yml
# Set the bind address to a specific IP (IPv4 or IPv6):
#
 network.host: 127.0.0.1
#
# Set a custom port for HTTP:
#
 http.port: 9200

3.5 Start service Elasticsearch và Enable server:

sudo systemctl start elasticsearch
sudo systemctl enable elasticsearch

4. Cài đặt Kibana (Version 4.4):

4.1 Tạo hoặc cập nhật yum repository cho Kibana:

sudo vi /etc/yum.repos.d/kibana.repo

4.2 Lưu nội dung file Yum repository:

[kibana-4.4]
name=Kibana repository for 4.4.x packages
baseurl=http://packages.elastic.co/kibana/4.4/centos
gpgcheck=1
gpgkey=http://packages.elastic.co/GPG-KEY-elasticsearch
enabled=1

4.3 Cài đặt Kibana:

sudo yum -y install kibana

4.4 Cấu hình Kibana:

sudo vi /opt/kibana/config/kibana.yml
# Kibana is served by a back end server. This setting specifies the port to use.
 server.port: 5601

# This setting specifies the IP address of the back end server.
 server.host: "localhost"

4.5 Start service Kibana và Enable server:

sudo systemctl start kibana
sudo chkconfig kibana on

4.6 Tạo và cấu hình Nginx để chạy giao diện web của Kibana:

sudo vi /etc/nginx/conf.d/kibana.conf
### KIBANA
server {
    listen 80;
    server_name kibana.lcdung.top;

    auth_basic "Restricted Access";
    auth_basic_user_file /etc/nginx/htpasswd.users;

    location / {
        proxy_pass http://localhost:5601;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;        
    }
}
sudo systemctl restart nginx

Lưu ý:

Tạo auth_basic_user_file dùng lệnh bên dưới với user name là kibanaadmin và password do bạn nhập vào.

sudo htpasswd -c /etc/nginx/htpasswd.users kibanaadmin

Sau đó chạy Domain thử để xem kết quả tuy nhiên tới bước này cũng chưa thấy thông tin gì đâu nếu status là Big Green thì thành công 50% rồi đó thấy Big Red thì fix đi nhé ^_^!

3. Kibana Dashboards

3.1 Beats dashboard

Elastic cung cấp một số Kibana dashboards  và Beats là pattern có thể giúp bạn bắt đầu làm quen với Kibana. Bài này sẽ không hướng dẫn sử dụng các kiểu dashboard mà chỉ một số thành phần cần thiết để sử dụng Filebeat.

1. Đầu tiên download beats dashboard về server:

cd ~
curl -L -O https://download.elastic.co/beats/dashboards/beats-dashboards-1.1.0.zip

2. Giải nén và load mẫu dashboards:

Install the unzip package with this command, visualizations và Beats index patterns trong Elasticsearch:

unzip beats-dashboards-*.zip
cd beats-dashboards-*
./load.sh

3. Load được các patterns sau:

  • [packetbeat-]YYYY.MM.DD
  • [topbeat-]YYYY.MM.DD
  • [filebeat-]YYYY.MM.DD
  • [winlogbeat-]YYYY.MM.DD

Khi vào giao diện Kibana, bạn sẽ chọn patter Filebeat làm mặc định.

3.2 Load Filebeat Index Template trong Elasticsearch

Kế hoạch là sử dụng Filebeat để chuyển logs đến Elasticsearch, nên nạp template Filebeat. Template sẽ cấu hình Elasticsearch để phân tích các dữ liệu Filebeat một cách thông minh.

1. Đầu tiên, download Filebeat vào folder home:

cd ~
curl -O https://gist.githubusercontent.com/thisismitch/3429023e8438cc25b86c/raw/d8c479e2a1adcea8b1fe86570e42abab0f10f364/filebeat-index-template.json

2. Sau đó, load template:

curl -XPUT 'http://localhost:9200/_template/filebeat?pretty' -d@filebeat-index-template.json

Output:

{
  "acknowledged" : true
}

Bây giờ Server ELK đã có thể nhận data Filebeat, Bắt đầu thiết lập Filebeat lên các client server để thu thập dữ liệu.

3.3 Cài đặt Filebeat vào Client Servers

Lưu ý: nếu bạn muốn sử dụng server local thì cài đặt Filebeat này lên server local luôn nhé

1. Copy SSL Certificate

Copy SSL từ Server ELK sang Server Client bằng lệnh sau:

scp /etc/pki/tls/certs/logstash-forwarder.crt user@client_server_private_address:/tmp

Trên Server Client, copy vào folder certs (/etc/pki/tls/certs):

sudo mkdir -p /etc/pki/tls/certs
sudo cp /tmp/logstash-forwarder.crt /etc/pki/tls/certs/

2. Cài đặt Filebeat Package

Trên server Client chạy các lệnh sau:

sudo rpm --import http://packages.elastic.co/GPG-KEY-elasticsearch
sudo vi /etc/yum.repos.d/elastic-beats.repo
[beats]
name=Elastic Beats Repository
baseurl=https://packages.elastic.co/beats/yum/el/$basearch
enabled=1
gpgkey=https://packages.elastic.co/GPG-KEY-elasticsearch
gpgcheck=1
sudo yum -y install filebeat

3. Cấu hình Filebeat

sudo vi /etc/filebeat/filebeat.yml

Cấu hình ở 5 phần sau:

#1 Path:

paths:
        - /var/log/*.log
        - /var/log/messages
        - /var/log/maillog
        - /var/log/nginx/*.log
        - /var/log/php-fpm/*.log

#2

document_type: syslog

#3

logstash:
    # The Logstash hosts
    hosts: ["localhost:5044"]

#4

    # default is 2048.
    bulk_max_size: 1024

#5

tls:
      # List of root certificates for HTTPS server verifications
      certificate_authorities: ["/etc/pki/tls/certs/logstash-forwarder.crt"]

Cuối cùng, start và enable Filebeat:

sudo systemctl start filebeat
sudo systemctl enable filebeat

4. Test Filebeat

Thực hiện trên Server ELK, chạy lệnh sau:

curl -XGET 'http://localhost:9200/filebeat-*/_search?pretty'

3.4 Kết nối với Kibana

Chứng thực vào giao diện web của Kibana chọn Index pattern làm mặc định.

Sau đó trở về Discover để khám phá cách sử dụng filter logs các kiểu nhé!

Tham khảo từ nguồn Digitalocean, Stackoverflow, Viblo.asia

Bài viết gốc được đăng tải tại lcdung.top

Có thể bạn quan tâm:

Xem thêm Việc làm Developer hấp dẫn trên TopDev

Những câu lệnh hữu ích trong Windows & DOS

Những câu lệnh hữu ích trong Windows & DOS

Bài viết được sự cho phép của vntesters.com

Bài viết dưới đây chia sẻ với các bạn những câu lệnh hữu ích và thông dụng trong Windows & DOS. Những lệnh này có thể được chạy từ cửa sổ CMD hoặc dialog RUN.

  20 trường hợp sử dụng lệnh Docker cho developer
  6 câu lệnh linux hay dùng trong phân tích log
To Access… Run Command
Accessibility Controls access.cpl
Accessibility Wizard accwiz
Add Hardware Wizard hdwwiz.cpl
Add/Remove Programs appwiz.cpl
Administrative Tools control admintools
Adobe Acrobat (if installed) acrobat
Adobe Designer (if installed) formdesigner
Adobe Distiller (if installed) acrodist
Adobe ImageReady (if installed) imageready
Adobe Photoshop (if installed) photoshop
Automatic Updates wuaucpl.cpl
Bluetooth Transfer Wizard fsquirt
Calculator calc
Certificate Manager certmgr.msc
Character Map charmap
Check Disk Utility chkdsk
Clipboard Viewer clipbrd
Command Prompt cmd
Component Services dcomcnfg
Computer Management compmgmt.msc
Control Panel control
Date and Time Properties timedate.cpl
DDE Shares ddeshare
Device Manager devmgmt.msc
Direct X Control Panel (if installed)* directx.cpl
Direct X Troubleshooter dxdiag
Disk Cleanup Utility cleanmgr
Disk Defragment dfrg.msc
Disk Management diskmgmt.msc
Disk Partition Manager diskpart
Display Properties control desktop
Display Properties desk.cpl
Display Properties (w/Appearance Tab Preselected) control color
Dr. Watson System Troubleshooting Utility drwtsn32
Driver Verifier Utility verifier
Event Viewer eventvwr.msc
Files and Settings Transfer Tool migwiz
File Signature Verification Tool sigverif
Findfast findfast.cpl
Firefox (if installed) firefox
Folders Properties folders
Fonts control fonts
Fonts Folder fonts
Free Cell Card Game freecell
Game Controllers joy.cpl
Group Policy Editor (XP Prof) gpedit.msc
Hearts Card Game mshearts
Help and Support helpctr
HyperTerminal hypertrm
Iexpress Wizard iexpress
Indexing Service ciadv.msc
Internet Connection Wizard icwconn1
Internet Explorer iexplore
Internet Properties inetcpl.cpl
Internet Setup Wizard inetwiz
IP Configuration (Display Connection Configuration) ipconfig /all
IP Configuration (Display DNS Cache Contents) ipconfig /displaydns
IP Configuration (Delete DNS Cache Contents) ipconfig /flushdns
IP Configuration (Release All Connections) ipconfig /release
IP Configuration (Renew All Connections) ipconfig /renew
IP Configuration (Refreshes DHCP & Re-Registers DNS) ipconfig /registerdns
IP Configuration (Display DHCP Class ID) ipconfig /showclassid
IP Configuration (Modifies DHCP Class ID) ipconfig /setclassid
Java Control Panel (if installed) jpicpl32.cpl
Java Control Panel (if installed) javaws
Keyboard Properties control keyboard
Local Security Settings secpol.msc
Local Users and Groups lusrmgr.msc
Logs You Out Of Windows logoff
Malicious Software Removal Tool mrt
Microsoft Access (if installed) msaccess
Microsoft Chat winchat
Microsoft Excel (if installed) excel
Microsoft Frontpage (if installed) frontpg
Microsoft Movie Maker moviemk
Microsoft Paint mspaint
Microsoft Powerpoint (if installed) powerpnt
Microsoft Word (if installed) winword
Microsoft Syncronization Tool mobsync
Minesweeper Game winmine
Mouse Properties control mouse
Mouse Properties main.cpl
Nero (if installed) nero
Netmeeting conf
Network Connections control netconnections
Network Connections ncpa.cpl
Network Setup Wizard netsetup.cpl
Notepad notepad
Nview Desktop Manager (if installed) nvtuicpl.cpl
Object Packager packager
ODBC Data Source Administrator odbccp32.cpl
On Screen Keyboard osk
Opens AC3 Filter (if installed) ac3filter.cpl
Outlook Express msimn
Paint pbrush
Password Properties password.cpl
Performance Monitor perfmon.msc
Performance Monitor perfmon
Phone and Modem Options telephon.cpl
Phone Dialer dialer
Pinball Game pinball
Power Configuration powercfg.cpl
Printers and Faxes control printers
Printers Folder printers
Private Character Editor eudcedit
Quicktime (If Installed) QuickTime.cpl
Quicktime Player (if installed) quicktimeplayer
Real Player (if installed) realplay
Regional Settings intl.cpl
Registry Editor regedit
Registry Editor regedit32
Remote Access Phonebook rasphone
Remote Desktop mstsc
Removable Storage ntmsmgr.msc
Removable Storage Operator Requests ntmsoprq.msc
Resultant Set of Policy (XP Prof) rsop.msc
Scanners and Cameras sticpl.cpl
Scheduled Tasks control schedtasks
Security Center wscui.cpl
Services services.msc
Shared Folders fsmgmt.msc
Shuts Down Windows shutdown
Sounds and Audio mmsys.cpl
Spider Solitare Card Game spider
SQL Client Configuration cliconfg
System Configuration Editor sysedit
System Configuration Utility msconfig
System File Checker Utility (Scan Immediately) sfc /scannow
System File Checker Utility (Scan Once At The Next Boot) sfc /scanonce
System File Checker Utility (Scan On Every Boot) sfc /scanboot
System File Checker Utility (Return Scan Setting To Default) sfc /revert
System File Checker Utility (Purge File Cache) sfc /purgecache
System File Checker Utility (Sets Cache Size to size x) sfc /cachesize=x
System Information msinfo32
System Properties sysdm.cpl
Task Manager taskmgr
TCP Tester tcptest
Telnet Client telnet
Tweak UI (if installed) tweakui
User Account Management nusrmgr.cpl
Utility Manager utilman
Windows Address Book wab
Windows Address Book Import Utility wabmig
Windows Backup Utility (if installed) ntbackup
Windows Explorer explorer
Windows Firewall firewall.cpl
Windows Magnifier magnify
Windows Management Infrastructure wmimgmt.msc
Windows Media Player wmplayer
Windows Messenger msmsgs
Windows Picture Import Wizard (need camera connected) wiaacmgr
Windows System Security Tool syskey
Windows Update Launches wupdmgr
Windows Version (to show which version of windows) winver
Windows XP Tour Wizard tourstart
Wordpad write
Bài viết gốc được đăng tải tại vntesters.com
Có thể bạn quan tâm:
Xem thêm Việc làm Developer hấp dẫn trên TopDev

Thêm Firebase vào Flutter và login với Facebook

Flutter Firebase login với Facebook

Bài viết được sự cho phép của tác giả Khiêm Lê

Tại sao cần phải login Facebook

Facebook – đây đã là cái tên không còn xa lạ gì nữa đối với mỗi chúng ta, nó đang là trang mạng xã hội lớn nhất hành tinh. Facebook không chỉ dừng lại ở đó, họ còn cung cấp các SDK, API cho các lập trình viên để có thể tích hợp các dịch vụ của mình vào các dịch vụ của họ, phổ biến nhất trong số đó là Login with Facebook.

Tại sao chúng ta cần phải tích hợp Facebook trong khi chúng ta có thể tự tạo một phương thức bảo mật riêng cho ứng dụng của mình? Có rất nhiều lý do, nhưng điều dễ thấy nhất chính là tính tiện dụng của nó. Hãy tưởng tượng bạn dùng một ứng dụng mà phải đăng ký tài khoản, rồi phải nhớ tài khoản, mật khẩu đăng nhập… Vậy tại sao bạn không dùng facebook login chỉ với một nút nhấn? Trong bài viết này mình sẽ hướng dẫn các bạn tích hợp facebook login vào flutter. Hãy cùng bắt đầu nào!

Tạo project Flutter

Đầu tiên, chúng ta cần có một project Flutter androidX và có tích hợp Firebase. Ở đây mình sử dụng androidX bởi vì trong bài viết mình có sử dụng một plugin để login facebook, nhưng plugin đó đã cũ đối với bản thông thường (không có androidX), do đó để tránh bị lỗi không mong muốn, mình khuyên bạn nên dùng androidX. Bạn nào có sẵn project rồi thì migrate sang androidX, bạn nào chưa thì tạo project theo lệnh “flutter create –androidx <project_name>”. Mình sẽ tạo project là flutter_login_with_facebook và tích hợp Firebase Authentication vào, bạn nào chưa biết thì có thể xem lại bài viết Thêm Firebase vào Flutter và login với Google.

Ứng dụng demo của mình như sau, mình muốn có một button Login with Facebook ở giữa màn hình, sau khi nhấn vào thì thực hiện đăng nhập. Sau khi đăng nhập thành công thì sẽ hiển thị message là “Logged in as <name>”, bên dưới là một button Logout. Code mình sẽ có như sau:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool _isLoggedIn = false;
  String _message;

  Future _loginWithFacebook() async {
    // TODO: handle login
  }

  Future _logout() async {
    // TODO: Handle logout
  }

  Future _checkLogin() async {
    // TODO: check if user logged in
  }

  @override
  void initState() {
    super.initState();
    _checkLogin();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: _isLoggedIn
            ? Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text(_message),
                  SizedBox(height: 12.0),
                  OutlineButton(
                    onPressed: () {
                      _logout();
                    },
                    child: Text('Logout'),
                  ),
                ],
              )
            : RaisedButton(
                onPressed: () {
                  _loginWithFacebook();
                },
                color: Colors.blue,
                textColor: Colors.white,
                child: Text('Login with Facebook'),
              ),
      ),
    );
  }
}

Vậy là phần giao diện coi như đã xong. Giờ chúng ta sẽ đến phần tạo app trên facebook developers và config app của chúng ta.

Tạo app trên facebook developers và config app

Đầu tiên, chúng ta cần tạo app trên facebook for developers trước. Các bạn truy cập developers.facebook.com, đăng nhập tài khoản developer của mình, nếu bạn chưa từng thì cứ dùng account facebook đăng nhập vào và thực hiện theo hướng dẫn. Ở trang chủ bạn vào My Apps và chọn Create App.

Tiếp theo, ở component Create a New App ID, các bạn nhập tên app vào, ví dụ như mình nhập là Flutter Login App. Lưu ý là tên này không được chứa tên thương hiệu ví dụ như trong tên mà có chữ Fb hay Facebook thì đều bị chặn. Phần Contact Email các bạn nhập email của các bạn vào và nhấn Create App ID.

Sau khi tạo xong, nó sẽ chuyển chúng ta về trang quản lý, ở trang này có mục Add product, các bạn tìm mục Facebook Login và chọn Set up.

Ở phần Quick Start, các bạn chọn Android hoặc iOS để bắt đầu thiết lập. Vì mình chỉ có máy Windows vậy nên mình chỉ có thể làm trên Android, sau này có điều kiện mình sẽ hướng dẫn sau. Các bạn chọn Android.

Sau khi nhấn vào nó sẽ đưa bạn đến một trang Setup dành cho Android. Ở phần setup này tổng cộng có 10 step, ở hai step đầu bạn không cần thực hiện, hãy nhấn Next và bỏ qua hai step này. Ở step thứ ba, bạn lấy applicationId trong file “android/app/build.gradle” và để vào Package Name, phần Default Activity Class Name bạn cũng dán vào và thêm .MainActivity vào sau đó nhấn Save.

Sau khi bạn nhấn Save, một component sẽ hiện ra thông báo là app của bạn không tồn tại trên CH Play (vì mình chưa deploy lên mà), bạn nhấn vào Use this package name để bỏ qua và nhấn Continue.

Ở step 4, bạn cần lấy Key Hash để thêm vào, cách thực hiện như sau:

  • Đều tiên bạn cần tải OpenSSL của Google. Bạn có thể tải tại đây. Sau khi tải về xong bạn giải nén ra ở một nơi nào đó.
  • Bạn tìm thư mục jre/bin trong thư mục cài đặt Android Studio (máy của mình sẽ là “C:\Program Files\Android\Android Studio\jre\bin”) và mở Git bash.
  • Bạn bấm copy đoạn code trong mục Windows, dán vào Git bash, bạn sửa lại PATH_TO_OPENSSL_LIBRARY thành đường dẫn đến thư mục chứa OpenSSL các bạn đã giải nén lúc nảy. Sửa USERNAME thành tên User của bạn. Ví dụ của mình là KhiemLe và path đến OpenSSL là “C:\OpenSSL\” thì mình sẽ chạy lệnh “keytool -exportcert -alias androiddebugkey -keystore “C:\Users\KhiemLe\.android\debug.keystore” | “C:\OpenSSL\bin\openssl” sha1 -binary | “C:\OpenSSL \bin\openssl” base64″
  • Nhập mật khẩu, mật khẩu mặc định sẽ là “android”. Bạn gõ “android” vào và nhấn Enter. Đoạn code sẽ xuất hiện bạn copy và dán vào phần Key Hashes, nhấn Save và Continue để tiếp tục.

Lưu ý: Nếu bạn chạy lệnh trên mà bị báo lỗi keytool command not found, bạn thêm ./ trước từ keytool. Nếu có lỗi nào khác phát sinh, hoặc là bạn kiểm tra độ dài mã Hash trả về không đúng 28 kí tự thì bạn chạy lệnh “./keytool -exportcert -alias androiddebugkey -keystore “C:\Documents and Settings\Administrator.android\debug.keystore” | “C:\OpenSSL\bin\openssl” sha1 -binary |”C:\OpenSSL\bin\openssl” base64″ Trong đó bạn thay “C:\OpenSSL” thành đường dẫn đến openSSL bạn đã tải về.

Ở step 5, nếu như bạn muốn thông báo của Android có thể mở ứng dụng của bạn thì bạn bật Enable Single Sign On, không thì để mặc định, nhấn Save và Continue.

Tiếp theo, step 6, cũng là step cuối cùng mà mình cần thực hiện. Đầu tiên bạn mở file “/android/app/src/main/res/values/strings.xml”, nếu chưa có thì bạn tạo ra file đó. Sau đó mở lên và bạn copy đoạn code Facebook đưa cho bạn vào thẻ resource. Ví dụ file strings.xml của mình như sau:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Flutter login with Facebook</string>
    <!--Bạn copy hai dòng đầu tiên dán vào đây-->
    <!--Ví dụ-->
    <string name="facebook_app_id">000000000000</string>
    <string name="fb_login_protocol_scheme">1111111111111</string>
</resources>

Bạn lưu lại và thoát ra. Tiếp theo, bạn mở file /android/app/src/main/AndroidManifest.xml, copy đoạn code “<uses-permission android:name=”android.permission.INTERNET”/>” thêm vào bên trong cặp thẻ chính manifest:

<manifest ...>
  <uses-permission android:name="android.permission.INTERNET"/>
  <!-- Orther codes-->
</manifest>

Tiếp theo, chúng ta sẽ thêm thẻ meta-data và custom chrome tab vào trong thẻ application. Bạn copy đoạn code ở mục số 5 và thêm vào. Code sẽ trông như như sau:

 <manifest>
    <!--Orther codes-->

    <application>
        <!--Orther codes-->

        <meta-data android:name="com.facebook.sdk.ApplicationId" 
                android:value="@string/facebook_app_id"/>
            
            <activity android:name="com.facebook.FacebookActivity"
                android:configChanges=
                        "keyboard|keyboardHidden|screenLayout|screenSize|orientation"
                android:label="@string/app_name" />
            <activity
                android:name="com.facebook.CustomTabActivity"
                android:exported="true">
                <intent-filter>
                    <action android:name="android.intent.action.VIEW" />
                    <category android:name="android.intent.category.DEFAULT" />
                    <category android:name="android.intent.category.BROWSABLE" />
                    <data android:scheme="@string/fb_login_protocol_scheme" />
                </intent-filter>
            </activity>
    </application>
</manifest>

Lưu tất cả các thay đổi lại và thoát ra. Giờ đến lúc chúng ta config firebase.

  Biết chọn gì đây? Flutter, React Native hay Xamarin?
  Chat app đơn giản với Flutter

Config Firebase Authentication

Để config Firebase Authentication login với Facebook các bạn cần lấy được App ID và App Secret, để lấy được các bạn làm như sau: ở trên trang facebook for developers các bạn tìm mục Basic trong tab Settings và nhấn vào. Các bạn sẽ thấy App ID và App Secret của mình ở đó, App Secret thì các bạn bấm vào Show thì nó mới hiện ra (có thể yêu cầu mật khẩu). Bạn copy hai cái đó và để tạm ở một nơi nào đó chút nữa mình sẽ dùng.

Một lưu ý cho các bạn là các bạn chỉ có thể đăng nhập facebook trên app bằng tài khoản developer, hoặc các bạn có thể thêm tài khoản developer, tester vào để test. Nếu app của bạn muốn deploy lên Google Play, bạn bắt buộc phải chọn Category app, cung cấp đường dẫn Privacy của bạn ở phần Privacy Policy URL và Terms of Service URL, sau đó Save changes lại. Tiếp theo là nhấn vào Enable ở chỗ Switch đang hiện OFF, nhập mật khẩu vào và xác nhận.

Tiếp theo, các bạn mở Firebase Console và mở project của các bạn đã thêm app của mình, tìm đến mục Authentication và nhấn vào tab Sign-in method. Ở trong tab này các bạn sẽ thấy một Provider là Facebook, các bạn bấm vào đó, chọn Enable. Sau đó, các bạn dán cái App ID và App Secret đã lấy ở trên vào hai mục phía dưới, đừng nhấn Save vội, bạn tiếp tục copy OAuth redirect URI rồi sau đó mới nhấn Save.

Sau khi Save lại, bạn tiếp tục quay về facebook for developers, tìm đến mục Settings trong product Facebook Login. Sau đó bạn dán URL bạn vừa copy vào mục Valid OAuth Redirect URIs và nhấn Save. Vậy là xem như chúng ta đã Setup xong phần facebook và firebase rồi. Giờ là lúc code flutter!

Code Flutter đăng nhập với Facebook và Firebase

Ở phía trên chúng ta đã có giao diện cho app rồi, giờ là lúc code xử lý. Đầu tiên, chúng ta cần thêm các dependencies trong file pubspec.yaml. Bạn mở file đó lên và thêm 3 dependencies sau:

dependencies:
  # file này thì nhớ để ý tab cho đúng nha
  firebase_core: ^0.4.0+9
  firebase_auth: ^0.14.0+5
  flutter_facebook_login: ^3.0.0
  # Orther codes

Lưu các thay đổi lại, nếu Code Editor không tự get dependencies thì bạn chạy lệnh “flutter packages get”. Giờ ta quay lại với file main.dart, chúng ta sẽ bắt đầu bằng việc thêm thư viện cần thiết trước.

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_facebook_login/flutter_facebook_login.dart';

Sau khi thêm thư viện xong thì trong class _MyHomePageState, bạn thêm cho mình hai biến final sau:

  final _auth = FirebaseAuth.instance;
  final _facebooklogin = FacebookLogin();

Bắt đầu với việc đăng nhập trước nhé, trong hàm _loginWithFacebook mình sẽ có code sau:

// Orther codes
  Future _loginWithFacebook() async {
    // Gọi hàm LogIn() với giá trị truyền vào là một mảng permission
    // Ở đây mình truyền vào cho nó quền xem email
    final result = await _facebooklogin.logIn(['email']);
    // Kiểm tra nếu login thành công thì thực hiện login Firebase
    // (theo mình thì cách này đơn giản hơn là dùng đường dẫn
    // hơn nữa cũng đồng bộ với hệ sinh thái Firebase, tích hợp được
    // nhiều loại Auth

    if (result.status == FacebookLoginStatus.loggedIn) {
      final credential = FacebookAuthProvider.getCredential(
        accessToken: result.accessToken.token,
      );
      // Lấy thông tin User qua credential có giá trị token đã đăng nhập
      final user = (await _auth.signInWithCredential(credential)).user;
      setState(() {
        _message = "Logged in as ${user.displayName}";
        _isLoggedIn = true; 
      });
    }
  }
// Orther codes

Giờ công việc đơn giản hơn nhiều đối với hai hàm còn lại. Mình sẽ có code như sau:

  Future _logout() async {
    // SignOut khỏi Firebase Auth
    await _auth.signOut();
    // Logout facebook
    await _facebooklogin.logOut();
    setState(() {
     _isLoggedIn = false; 
    });
  }

  Future _checkLogin() async {
    // Kiểm tra xem user đã đăng nhập hay chưa
    final user = await _auth.currentUser();
    if (user != null) {
      setState(() {
        _message = "Logged in as ${user.displayName}";
       _isLoggedIn = true; 
      });
    }
  }

Vậy là chúng ta đã hoàn tất công việc rồi. Bạn có thể chạy app để test thử. Lưu ý là bạn nên test trên máy ảo có cài CH Play hoặc máy thật để tránh lỗi không mong muốn. Nếu như bạn muốn cài trên máy thật, bạn phải bật USB debugging và cho phép máy tính truy cập bộ nhớ. Và trên máy tính bạn phải cài Google USB driver nhé. Giờ hãy cùng xem kết quả nào!

Lưu ý: nếu bạn nào đăng nhập không được thì nó đã bị sai Hash key rồi. Cách mà mình xử lý như sau:

Bạn thêm đoạn code “_facebooklogin.loginBehavior = FacebookLoginBehavior.webViewOnly;” vào trước dòng code “final result = await _facebooklogin.logIn([’email’]);” Sau đó bạn reload lại app, tiến hành đăng nhập. Lúc này sẽ không phải đăng nhập bằng app facebook trên điện thoại của bạn nữa mà nó sẽ mở một webview để chúng ta đăng nhập. Bạn đăng nhập tài khoản facebook của bạn vào. Sau khi đăng nhập xong facebook sẽ yêu cầu bạn cấp quyền cho ứng dụng truy cập vào tài khoản của bạn, bạn nhấn tiếp tục.

Sau khi đã đăng nhập thành công ứng dụng sẽ hiện ra dòng chữ Logged in as <your_name>. Bạn nhấn Logout. Quay lại code lúc nảy và thay webViewOnly thành nativeOnly, reload lại app và nhấn đăng nhập. Lúc này trên màn hình sẽ hiện lên lỗi và có kèm cả Hash Key,

Các bạn đem cái hash key đó dán vào trong cái hash key ban đầu các bạn đã thêm trong facebook login. Tiến hành đăng nhập lại. Lần này chắc chắn sẽ không lỗi nữa.

Theo như mặc định nếu máy bạn có cài app facebook thì sẽ hỏi quyền đăng nhập thôi, nếu máy bạn chưa cài thì nó sẽ hiện webview cho các bạn đăng nhập. Hoặc bạn có thể chỉnh lại như trên mình vừa thực hiện.

Tổng kết

Vậy là trong bài viết này mình đã hướng dẫn các bạn tích hợp đăng nhập facebook vào ứng dụng flutter cùng với firebase. Các bạn có thể xem lại code tại đây. Nếu các bạn có bất kỳ thắc mắc nào, đừng ngần ngại comment phía bên dưới bài viết, mình sẽ hỗ trợ các bạn. Nếu bạn thấy bài viết hay, đừng quên chia sẻ với bạn bè để mọi người cùng học nha. Cảm ơn các bạn đã đọc bài viết!

Bài viết gốc được đăng tải tại khiemle.dev

Có thể bạn quan tâm:

Xem thêm IT Jobs for Developer hấp dẫn trên TopDev

Chưa Có Kinh Nghiệm, Làm Thế Nào Để Cover Letter Ấn Tượng Hơn?

cover letter cho người chưa có kinh nghiệm
Làm Thế Nào Để Cover Letter Ấn Tượng Hơn Với Những Ứng Viên Chưa Có Kinh Nghiệm?

Cover letter được nhiều nhà tuyển dụng đánh giá cao vì nó là phần đầu tiên giúp đánh giá về sự chuyên nghiệp của ứng viên. Chính vì thế, bỏ thời gian đầu tư cho cover letter chắc chắn sẽ giúp bạn dễ dàng “lọt vào tầm mắt” của nhà tuyển dụng hơn. Tuy nhiên với những ứng viên chưa có kinh nghiệm làm việc, viết cover letter như thế nào để thuyết phục nhà tuyển dụng chắc chắn là vấn đề khó khăn hơn. Cùng TopDev tìm hiểu thêm về cách viết cover letter cho người chưa có kinh nghiệm với bài viết dưới đây nhé!

cover letter cho người chưa có kinh nghiệm
Cách viết cover letter cho người chưa có kinh nghiệm

Cover letter là gì? Tại sao cần có cover letter?

Cover letter hay còn gọi là thư xin việc, là thư được ứng viên viết và gửi kèm với đơn xin việc (CV). Ngày nay đa phần việc ứng tuyển đều được thực hiện qua email nên cover letter cũng chính là nội dung email khi bạn gửi CV cho nhà tuyển dụng. Nội dung cover letter chủ yếu đề cập một cách tổng quan về những điểm mạnh và các kỹ năng mà bạn cho là phù hợp với vị trí mà mình đang ứng tuyển.

Bên cạnh CV, cover letter cũng là một nội dung rất quan trọng để ứng viên gây ấn tượng với nhà tuyển dụng. Với những công ty có yêu cầu tuyển dụng khắt khe, cover letter thậm chí còn là yếu tố quyết định để nhà tuyển dụng quyết định có nên xem CV của bạn và triển khai các bước tiếp theo hay không. Chính vì thế, những email xin việc thiếu đi cover letter cũng đồng nghĩa với việc mất đi rất nhiều cơ hội được thông qua vị trí đó.

Ứng viên chưa có kinh nghiệm nên viết cover letter như thế nào cho ấn tượng?

Cung cấp đầy đủ các thông tin cần thiết trong cover letter

Trước khi muốn tạo ấn tượng với nhà tuyển dụng hay muốn cover letter nổi bật hơn so với ứng viên, bạn cần phải đảm bảo đã cung cấp đầy đủ thông tin trong cover letter của mình. Những thông tin đó gồm:

  • Thông tin người gửi (Tên bạn, địa chỉ, số điện thoại, email….)
  • Thông tin người nhận (Tên người nhận, vị trí làm việc, email, công ty,…)

Xem thêm Một bộ hồ sơ xin việc qua email có những gì?

Trình bày nội dung rõ ràng và đẹp mắt

Bạn có thể chưa có kinh nghiệm nhưng sự chỉn chu và nghiêm túc thì bạn hoàn toàn đủ để đáp ứng – điều này sẽ được thể hiện thông qua cách mà ứng viên trình bày cover letter của mình. Theo đó, trước khi nhấn gửi email, hãy kiểm tra một cách cẩn thận toàn bộ nội dung mình đã viết, đảm bảo sự logic và không dư thừa, đúng chính tả, không bị lỗi font,…

Nội dung nên chia thành nhiều đoạn nhỏ, với những ý riêng biệt để không khiến người đọc cảm thấy quá nhiều chữ và làm giảm đi sự hứng thú của họ. Nếu gửi cover letter thành một file riêng thì tốt hơn hết hãy gửi ở dạng file PDF để khi nhà tuyển dụng xem sẽ không bị lỗi font chữ do không tương thích với thiết bị của nhà tuyển dụng.

cover letter viết như thế nào cho hiệu quả

Xem ngay các việc làm IT intern không đòi hỏi kinh nghiệm tên TopDev

Thể hiện được đam mê với công việc

Thật ra nhà tuyển dụng không ngại đào tạo một nhân viên chưa có kinh nghiệm, điều khiến họ ái ngại là sự thiếu nhiệt huyết và dễ chán nản của bạn. Chính vì thế, hãy cho công ty ứng tuyển thấy rằng, dù bạn chưa có kinh nghiệm làm việc nhưng bạn luôn trong trạng thái sẵn sàng học hỏi bất cứ lúc nào, bất cứ nơi đâu.

Sự năng nổ và nhiệt huyết là lợi thế lớn nhất của người trẻ. Hãy tận dụng tối đa nó để gây ấn tượng với nhà tuyển dụng. Hãy thuyết phục nhà tuyển dụng rằng bạn luôn dành 100% tinh thần và sức lực để đáp ứng những kì vọng mà họ đặt ra.

Đề cập đến mục tiêu nghề nghiệp một cách rõ ràng

Bạn có thể chưa có kinh nghiệm làm việc nhưng chắc chắn bạn phải xác định được lĩnh vực mà mình muốn làm việc và chức vụ công việc mà bạn muốn chinh phục. Lĩnh vực bạn muốn làm đã thể hiện thông qua vị trí bạn ứng tuyển. Thêm vào đó, hãy đề cập đến mục tiêu công việc một cách cụ thể thông qua chức vụ mà bạn muốn đạt được.

Mẫu câu thông dụng là “Tôi muốn trong vòng 2 năm tới có thể đạt được vị trí…” chắc chắn sẽ giúp cover letter của bạn nổi bật hơn so với những ứng viên khác. Nó cho thấy sự tâm huyết của bạn trong công việc cũng như việc bạn đã lên kế hoạch cụ thể với lộ trình nghề nghiệp của mình.

viết cover letter rõ ràng

Tuy nhiên, cũng cần lưu ý đừng quá sa đà đến vấn đề này và đề cập đến những vị trí quá cao mà một người chưa có kinh nghiệm khó có thể làm được. Tốt nhất nên đề cập đến kế hoạch trong khoảng từ 1 – 3 năm trở lại. Hãy cho nhà tuyển dụng thấy được sự tự tin của bạn vào năng lực của bản thân. Đó chính là điều bất cứ công ty nào cũng đang tìm kiếm.

  Kỹ Năng Giao Tiếp Trong Tuyển Dụng Và Cách Cải Thiện

Giới thiệu về trình độ học vấn và những thành tích hoạt động thời sinh viên

Với những ứng viên mới ra trường chưa có kinh nghiệm, đây cũng là cách để bạn ghi điểm với nhà tuyển dụng. Bạn có thể đề cập đến trình độ học vấn như xếp loại tốt nghiệp, nhưng chỉ nên áp dụng với những ứng viên có kết quả tốt nghiệp tốt như từ loại khá trở lên để gây ấn tượng với nhà tuyển dụng.

Ngoài ra, nếu bạn là người năng nổ trong các hoạt động Đoàn Hội, tình nguyện thời còn sinh viên thì đây cũng có thể là một điểm cộng. Có thể giới thiệu sơ qua về những hoạt động bạn tham gia và những kinh nghiệm nào bạn tích lũy được từ hoạt động đó. Nếu các hoạt động và kỹ năng có được liên quan đến công việc bạn đang ứng tuyển thì càng tốt.

  Cách viết CV dành cho Software Developer

Cover letter là một phần cần thiết và nên có trong bất cứ email xin việc hay hồ sơ ứng tuyển nào. Một cover letter chuyên nghiệp và có sự đầu tư sẽ giúp bạn ghi điểm với nhà tuyển dụng và dễ dàng hơn trong việc thông qua vị trí đã ứng tuyển. Tìm đọc thêm các bài viết hữu ích khác cùng TopDev để tích lũy thêm kinh nghiệm cho bản thân mình nhé!

Có thể bạn quan tâm:

Xem thêm việc làm Developer hấp dẫn trên TopDev

Ngăn xếp (stack) và hàng đợi (queue) trong C++

Stack (ngăn xếp) và Queue (hàng đợi) khác nhau như thế nào?

Ngăn xếp và hàng đợi là hai kiểu cấu trúc dữ liệu động được sử dụng khá phổ biến trong lập trình. Hai kiểu cấu trúc này đều được xây dựng dựa trên danh sách liên kết đơn cho nên khá dễ để cài đặt. Hãy cùng tìm hiểu xem stack và queue là gì và phân biệt hai cấu trúc dữ liêu này trong bài viết dưới đây bạn nhé!

Khái niệm ngăn xếp

Ngăn xếp hay Stack là một dạng danh sách liên kết hoạt động theo cơ chế LIFO (Last In, First Out), nghĩa là các phần tử được thêm vào sau cùng thì sẽ là phần tử được lấy ra đầu tiên.

Một con trỏ đặc biệt gọi là top được sử dụng để theo dõi phần tử được thêm vào gần nhất trong Stack.

Các thao tác cơ bản trên ngăn xếp như sau:

  • IsEmpty: kiểm tra xem ngăn xếp có rỗng hay không
  • Push: thêm phần tử vào trên cùng ngăn xếp
  • Pop: lấy phần tử nằm trên cùng ra khỏi ngăn xếp và trả về giá trị của phần tử đó (nếu ngăn xếp không rỗng)
  • Top: trả về giá trị của phần tử nằm trên cùng của ngăn xếp (nếu ngăn xếp không rỗng)

Khái niệm hàng đợi

Hàng đợi hay queue là một cấu trúc dữ liệu động hoạt động theo cơ chế FIFO (First In, First Out), nghĩa là phần tử được thêm vào đầu tiên sẽ là phần tử được lấy ra đầu tiên.

Queue sử dụng hai con trỏ: con trỏ front chỉ đến phần tử đầu tiên được chèn vào, trong khi con trỏ rear chỉ đến phần tử được chèn vào cuối cùng.

Các thao tác cơ bản trên hàng đợi:

  • IsEmpty: kiểm tra hàng đợi có rỗng hay không
  • EnQueue: thêm một phần tử vào cuối hàng đợi
  • DeQueue: lấy phần tử đầu tiên ra khỏi hàng đợi (nếu hàng đợi không rỗng)
  • Front: trả về giá trị của phần tử ở đầu hàng đợi (nếu hàng đợi không rỗng)

Điểm chung của Ngăn xếp và Hàng đợi

Stack và Queue có nhiều điểm chung dù chúng hoạt động theo các nguyên tắc khác nhau. Đầu tiên, cả Stack và Queue đều là cấu trúc dữ liệu tuyến tính, nghĩa là các phần tử trong chúng được lưu trữ và truy cập theo một thứ tự xác định. Cả hai đều có thể lưu trữ các phần tử của cùng một kiểu dữ liệu, chẳng hạn như số nguyên, chuỗi, hoặc các đối tượng.

Cả Stack và Queue đều hỗ trợ các thao tác cơ bản như thêm (insert) và xóa (delete) phần tử. Trong Stack, thao tác thêm và xóa diễn ra ở cùng một đầu (đỉnh – top), trong khi Queue sử dụng hai đầu (front và rear) để thực hiện các thao tác này. Tuy nhiên, về cơ bản, cả hai đều dựa vào việc sử dụng con trỏ để theo dõi các phần tử trong cấu trúc dữ liệu.

Cả Stack và Queue đều không cho phép lặp lại phần tử đã được truy cập, vì chúng hoạt động theo thứ tự và yêu cầu phần tử được xử lý theo nguyên tắc FIFO (First In, First Out) trong Queue, hoặc LIFO (Last In, First Out) trong Stack.

Trong C++, cả hai cấu trúc dữ liệu này đều có thể được triển khai dễ dàng thông qua Thư viện Mẫu Chuẩn (STL), với các thao tác tương tự như push(), pop(), và empty(). Cả Stack và Queue đều đóng vai trò quan trọng trong nhiều bài toán về quản lý dữ liệu, thuật toán, và cấu trúc điều khiển, giúp lập trình viên xử lý các vấn đề phức tạp một cách hiệu quả.

Bảng so sánh Stack và Queue

so sánh Stack và Queue

Dưới đây là bảng so sánh sự khác nhau giữa Stack và Queue:

Tham số Cấu trúc dữ liệu Stack Cấu trúc dữ liệu Queue
Cơ bản Là cấu trúc dữ liệu tuyến tính. Phần tử được thêm vào và xóa khỏi cùng một đầu. Là cấu trúc dữ liệu tuyến tính. Phần tử được thêm vào và xóa khỏi hai đầu khác nhau.
Nguyên tắc hoạt động Tuân theo nguyên tắc LIFO (Last In, First Out). Phần tử được chèn cuối cùng sẽ bị xóa trước tiên. Tuân theo nguyên tắc FIFO (First In, First Out). Phần tử được thêm vào đầu tiên sẽ bị xóa trước tiên.
Con trỏ Có một con trỏ duy nhất – top. Con trỏ này chỉ đến phần tử trên cùng của stack. Có hai con trỏ – frontrear. rear chỉ đến phần tử được chèn cuối cùng, còn front chỉ đến phần tử được thêm đầu tiên.
Các thao tác Stack sử dụng các thao tác pushpop. pop xóa phần tử, push chèn phần tử. Queue sử dụng các thao tác enqueuedequeue. dequeue xóa phần tử, enqueue chèn phần tử.
Cấu trúc Việc thêm và xóa phần tử chỉ diễn ra ở một đầu, gọi là top. Sử dụng hai đầu – frontrear. rear dùng để chèn phần tử, front dùng để xóa phần tử.
Kiểm tra điều kiện đầy Khi top == max-1, stack đã đầy. Khi rear == max-1, queue đã đầy.
Kiểm tra điều kiện rỗng Khi top == -1, stack rỗng. Khi front == rear + 1 hoặc front == -1, queue rỗng.
Biến thể Không có các loại biến thể nào của Stack. Queue có ba loại biến thể – circular queue, priority queue, và double-ended queue.
Trực quan Stack được hình dung như một tập hợp các phần tử theo chiều dọc. Queue được hình dung như một tập hợp các phần tử theo chiều ngang.
Triển khai Triển khai đơn giản hơn so với Queue. Triển khai phức tạp hơn so với Stack.

Tham khảo bài viết gốc: https://byjus.com/gate/difference-stack-and-queue-data-structures/

Vậy là trong bài này, mình đã giới thiệu cho các bạn thêm hai cấu trúc dữ liệu phổ biến đó chính là ngăn xếp (stack) và hàng đợi (queue) và bảng so sánh tóm tắt sự khác nhau của hai cấu trúc dữ liệu này. Nếu các bạn thấy bài viết này hay, đừng quên chia sẻ cho bạn bè cùng biết, cảm ơn các bạn đã theo dõi bài viết!

Xem thêm Việc làm Developer hấp dẫn trên TopDev

Hibernate Batch processing

hibernate

Bài viết được sự cho phép của tác giả Giang Phan

Trong bài này tôi sẽ giới thiệu với các bạn 1 phần quan trọng của Hibernate trong quá trình thao tác với cơ sở dữ liệu đó chính là batch processing hay còn gọi là xử lý hàng loạt.

Hibernate Batch Processing là gì?

Xem xét một tình huống khi chúng ta cần tải insert một số lượng lớn các bản ghi vào cơ sở dữ liệu bằng cách sử dụng Hibernate.

try (SessionFactory sessionFactory = HibernateUtils.getSessionFactory();
Session session = sessionFactory.openSession();) {

session.beginTransaction();

final int numberOfRecords = 10000000;
for ( int i=0; i < numberOfRecords; i++ ) {
Tag tag = new Tag();
tag.setName("Hibernate Batch Processing");
session.persist(tag);
}

session.getTransaction().commit();
}
Khi chạy chương trình trên, chúng ta sẽ gặp một exception OutOfMemmeryException, vì Hibernate sẽ lưu tất cả 10000000 bản ghi vào bộ nhớ L1 Cache nhưng bộ nhớ cache không đủ.

Để giải quyết vấn đề này, chúng ta sẽ sử dụng tính năng batch processing trong Hibernate. Tính năng này cho phép chúng ta gửi yêu cầu thực thi một lượng lớn câu lệnh SQL một lần, thay vì yêu cầu thực thi từng câu lệnh SQL một.

Hibernate GenerationType

Chúng ta cần nắm về GenerationType trước khi tìm hiểu cách sử dụng Hibernate Batch Processing.

Như đã giới thiệu ở bài viết “Các Annotation của Hibernate“, chúng ta có 4 loại generated identifier strategies: AUTO, IDENTITY, SEQUENCE, TABLE.

  • AUTO : loại này sẽ sử dụng một trong 3 loại IDENTITY, SEQUENCE hoặc TABLE dựa trên yêu cầu tương thích của database.
  • IDENTITY : sẽ map với auto-incremented column.
  • SEQUENCE : được sử dụng để ủy quyền tạo id cho cơ sở dữ liệu.
  • TABLE : không có implement trực tiếp trong cơ sở dữ liệu quan hệ. TABLE không mở rộng (scale) khi tăng số lượng kết nối đến cơ sở dữ liệu. Hơn nữa, ngay cả đối với một kết nối cơ sở dữ liệu, thời gian phản hồi tạo định danh lớn hơn 10 lần so với khi sử dụng IDENTITY hoặc SEQUENCE. Chi tiết các bạn tham khảo thêm bài viết “Why you should never use the TABLE identifier generator with JPA and Hibernate“. Vì lý do performance và scalability, chúng ta nên xem xét 2 yếu tố này trước khi sử dụng.
  • Ngoài ra, chúng ta có thể sử dụng UUID : là một lớp của Java cho phép bạn tạo ra một chuỗi 36 ký tự ngẫu nhiên.

Với Oracle, PostgreSQL, Hibernate nó sẽ gọi một Sequence có tên Hibernate_Sequence để tạo ra một giá trị tăng dần để gán giá trị cho cột này. Với các DB khác chẳng hạn như MySQL, DB2, SQL Server, Sysbase cột có thể là kiểu IDENTITY và giá trị của nó có thể tự tăng.

IDENTITY generator sẽ disable Hibernate sử dụng JDBC batch insert. JDBC batch updates và delete sẽ không bị ảnh hưởng bởi IDENTITY generator.

Chỉ các câu lệnh INSERT không thể được tạo batch bởi vì:

  • Hibernate cố gắng trì hoãn Persistence Context flush SQL cho đến giây phút cuối cùng có thể. Chiến lược này gọi là “transactional write behind”. Vào thời điểm Persistence Context flush, các câu lệnh INSERT đã được thực thi để Hibernate biết id Entity nào được gán cho các Entity được lưu trữ.
  • Các thay đổi đã flush chỉ hiển thị cho transaction hiện tại. Cho đến khi trasaction hiện tại được commit, không có thay đổi nào được nhìn thấy bởi các transaction đồng thời khác.
  • IDENTITY cho phép một cột số nguyên / bigint được tự động tăng theo yêu cầu. Quá trình gia tăng xảy ra bên ngoài transaction đang chạy hiện tại, do đó, việc rollback có thể được thực hiện để loại bỏ các giá trị đã gán (khoảng cách giá trị có thể xảy ra). Quá trình tự tăng rất hiệu quả vì nó sử dụng cơ chế khóa bên trong cơ sở dữ liệu. Hạn chế duy nhất là chúng ta không thể biết giá trị mới được gán trước khi thực hiện câu lệnh INSERT. Hạn chế này đang cản trở cơ chế “transactional write behind” của Hibernate. Vì lý do này, Hibernates vô hiệu hóa hỗ trợ lô JDBC cho Entity sử dụng IDENTITY generator.
  • TABLE không tốt về performance. Do đó, sử dụng IDENTITY vẫn là lựa chọn tốt nhất trên MySQL. Nếu cần tính năng batch trên MySQL, chúng ta có thể kết hợp Hibernate sử dụng jOOQ.

Do đó, cách tốt nhất để sử dụng Hibernate Batch Processing trong Hibernate là sử dụng SEQUENCE generator hoặc UUID.

Enable Hibernate Batch processing

Trong bài này, tôi sẽ sử dụng PostgreSQL để sử dụng tính năng Hibernate Batch Processing.

-- Database: gp_batch_processing
-- DROP DATABASE gp_batch_processing;
CREATE DATABASE gp_batch_processing
WITH
OWNER = postgres
ENCODING = 'UTF8'
LC_COLLATE = 'C'
LC_CTYPE = 'C'
TABLESPACE = pg_default
CONNECTION LIMIT = -1;

-- SEQUENCE: public.tag_id_seq
-- DROP SEQUENCE public.tag_id_seq;
CREATE SEQUENCE public.tag_id_seq
INCREMENT 1
START 1
MINVALUE 1
MAXVALUE 9223372036854775807
CACHE 1;

ALTER SEQUENCE public.tag_id_seq
OWNER TO postgres;

-- Table: public.tag
-- DROP TABLE public.tag;
CREATE TABLE public.tag
(
id bigint NOT NULL DEFAULT nextval('tag_id_seq'::regclass),
name character varying COLLATE pg_catalog."default",
CONSTRAINT tag_pkey PRIMARY KEY (id)
)

TABLESPACE pg_default;

ALTER TABLE public.tag
OWNER to postgres;

Tạo maven project, mở file pom.xml thêm các denpendency sau:

<project xmlns="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://maven.apache.org/xsd/maven-4.0.0.xsd">     <modelVersion>4.0.0</modelVersion>     <groupId>com.gpcoder</groupId>     <artifactId>HibernateBatchProcessingTutorial</artifactId>     <version>0.0.1-SNAPSHOT</version>     <packaging>jar</packaging>     <name>HibernateBatchProcessingTutorial</name>     <url>http://maven.apache.org</url>     <properties>         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>         <maven.compiler.source>1.8</maven.compiler.source>         <maven.compiler.target>1.8</maven.compiler.target>     </properties>     <dependencies>         <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->         <dependency>             <groupId>org.projectlombok</groupId>             <artifactId>lombok</artifactId>             <version>1.18.10</version>         </dependency>         <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->         <dependency>             <groupId>org.hibernate</groupId>             <artifactId>hibernate-core</artifactId>             <version>5.4.7.Final</version>         </dependency>         <!-- https://mvnrepository.com/artifact/postgresql/postgresql -->         <dependency>             <groupId>postgresql</groupId>             <artifactId>postgresql</artifactId>             <version>9.1-901.jdbc4</version>         </dependency>         <!-- Log4j2 -->         <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->         <dependency>             <groupId>org.apache.logging.log4j</groupId>             <artifactId>log4j-api</artifactId>             <version>2.13.1</version>         </dependency>         <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->         <dependency>             <groupId>org.apache.logging.log4j</groupId>             <artifactId>log4j-core</artifactId>             <version>2.13.1</version>         </dependency>     </dependencies> </project>

Trong ví dụ, tôi thêm thư viện Log4j2 để log SQL và parameter được tạo bởi Hibernate. Tạo file log4j2.xml trong thư mục resource:

<?xml version="1.0" encoding="UTF-8"?> <Configuration>     <Properties>         <Property name="logFolder">logs</Property>     </Properties>     <Appenders>         <!-- Console Appender -->         <Console name="Console" target="SYSTEM_OUT">             <PatternLayout pattern="%d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %msg%n" />         </Console>         <!-- Hibernate File Appender -->         <File name="HibernateFile" fileName="${logFolder}/hibernate.log">             <PatternLayout pattern="%d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %msg%n" />         </File>         <!-- GPCoder File Appender -->         <File name="GPCoderAppFile" fileName="${logFolder}/gpcoderApp.log">             <PatternLayout pattern="%d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %msg%n" />         </File>     </Appenders>     <Loggers>         <!-- Log everything in hibernate -->         <Logger name="org.hibernate" level="info" additivity="false">             <AppenderRef ref="Console" />             <AppenderRef ref="HibernateFile" />         </Logger>         <!-- Log SQL statements -->         <Logger name="org.hibernate.SQL" level="debug" additivity="false">             <AppenderRef ref="Console" />             <AppenderRef ref="HibernateFile" />         </Logger>         <!-- Parameters which are bound to SQL statements (ie. WHERE clause) -->         <Logger name="org.hibernate.type.descriptor.sql" level="trace" additivity="false">             <AppenderRef ref="Console"/>             <AppenderRef ref="HibernateFile" />         </Logger>         <!-- Log GP Coder App statements -->         <Logger name="com.gpcoder" level="debug" additivity="false">             <AppenderRef ref="Console" />             <AppenderRef ref="GPCoderAppFile" />         </Logger>         <Root level="error">             <AppenderRef ref="Console" />             <AppenderRef ref="GPCoderAppFile" />         </Root>     </Loggers> </Configuration>

Tạo file hibernate.cfg.xml và cấu hình như sau:

<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration>     <session-factory>         <!-- Database setting -->         <property name="connection.username">postgres</property>         <property name="connection.password">1234567</property>         <property name="connection.driver_class">org.postgresql.Driver</property>         <property name="connection.url">jdbc:postgresql://localhost:5432/gp_batch_processing</property>         <!-- JDBC connection pool (use the built-in) -->         <property name="connection.pool_size">4</property>         <!-- SQL dialect -->         <property name="dialect">org.hibernate.dialect.PostgreSQL94Dialect</property>         <!-- Enable Hibernate's automatic session context management -->         <property name="current_session_context_class">thread</property>         <!-- Show all executed SQL to console -->         <property name="jdbc.batch_size">10</property>         <property name="jdbc.order_inserts">true</property>         <property name="jdbc.order_updates">true</property>         <property name="generate_statistics">true</property>         <mapping class="com.gpcoder.entities.Tag" />     </session-factory> </hibernate-configuration>

Lưu ý:

  • Chúng ta bắt buộc phải thêm thuộc tính hibernate.jdbc.batch_size để enable tính năng Batch processing của Hibernate. Giá trị này phải lớn hơn 0. Thông thường người ta thường gán giá trị trong khoảng 10-30.
  • generate_statistics : sử dụng để theo thống kê số lượng SQL được thực thi, batch processing.
  • order_inserts, order_updates : Nói với Hibernate rằng hãy order câu lệnh INSERT và UPDATE tương tự nhau để improve performance.

Tạo Entity class:

Tag.java

package com.gpcoder.entities; import lombok.Data; import javax.persistence.*; import java.io.Serializable; import java.util.Set; @Data @Entity @Table public class Tag implements Serializable {     @Id     @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tag_generator")     @SequenceGenerator(name="tag_generator", sequenceName = "tag_id_seq", allocationSize=1)     private Long id;     @Column     private String name; }

Hibernate Batch Insert

Mọi thứ cấu hình Hibrernate Batch Processing đã xong. Các bạn xem chương trình bên dưới để xem cách sử dụng Batch Insert với Hibernate.

package com.gpcoder; import com.gpcoder.entities.Tag; import com.gpcoder.utils.HibernateUtils; import lombok.extern.log4j.Log4j2; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.stat.Statistics; @Log4j2 public class HibernateBatchProcessingExample {     public static void main(String[] args) {         try (SessionFactory sessionFactory = HibernateUtils.getSessionFactory();              Session session = sessionFactory.openSession();) {             log.info("Statistics enabled = " + sessionFactory.getStatistics().isStatisticsEnabled());             session.beginTransaction();             final int numberOfRecords = 30;             final int batchSize = 10; // same as the JDBC batch size             for (int i = 1; i <= numberOfRecords; i++) {                 Tag tag = new Tag();                 tag.setName("Hibernate Batch Processing " + i);                 session.persist(tag);                 if (i % batchSize == 0 && i != numberOfRecords) {                     log.info("Flush a batch of INSERT & release memory: {} time(s)", (i / batchSize));                     session.flush();                     session.clear();                 }             }             log.info("Flush the last time at commit time");             session.getTransaction().commit();         }     } }

Lưu ý:

  • Chúng ta sẽ sử dụng các phương thức flush() và clear() của đối tượng Session để Hibernate tiếp tục bản ghi này vào cơ sở dữ liệu thay vì lưu trữ chúng trong bộ nhớ cache. Nhờ vậy, bộ nhớ được giải phóng và không gặp vấn đề OutOfMemmeryException.
  • batchSize: chúng ta nên gán nó bằng với giá trị batch_size chúng ta đã cấu hình trong hibernate.cf.xml để đạt được performance tốt nhất.
  • Chúng ta không cần flush() ở vòng lặp cuối cùng vì nó mặc định sẽ được thực thi khi lệnh commit được gọi.

Output chương trình:

2020-Apr-02 16:30:47 PM [main] INFO  org.hibernate.Version - HHH000412: Hibernate Core {5.4.7.Final} 2020-Apr-02 16:30:47 PM [main] INFO  org.hibernate.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.0.Final} 2020-Apr-02 16:30:47 PM [main] WARN  org.hibernate.orm.connections.pooling - HHH10001002: Using Hibernate built-in connection pool (not for production use!) 2020-Apr-02 16:30:47 PM [main] INFO  org.hibernate.orm.connections.pooling - HHH10001005: using driver [org.postgresql.Driver] at URL [jdbc:postgresql://localhost:5432/gp_batch_processing] 2020-Apr-02 16:30:47 PM [main] INFO  org.hibernate.orm.connections.pooling - HHH10001001: Connection properties: {user=postgres, password=****} 2020-Apr-02 16:30:47 PM [main] INFO  org.hibernate.orm.connections.pooling - HHH10001003: Autocommit mode: false 2020-Apr-02 16:30:47 PM [main] INFO  org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl - HHH000115: Hibernate connection pool size: 4 (min=1) 2020-Apr-02 16:30:47 PM [main] INFO  org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.PostgreSQL94Dialect 2020-Apr-02 16:30:48 PM [main] INFO  com.gpcoder.HibernateBatchProcessingExample - Statistics enabled = true 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] INFO  com.gpcoder.HibernateBatchProcessingExample - Flush a batch of INSERT & release memory: 1 time(s) 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 1] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [2] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 2] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [3] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 3] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [4] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 4] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [5] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 5] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [6] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 6] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [7] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 7] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [8] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 8] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [9] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 9] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [10] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 10] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [11] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] INFO  com.gpcoder.HibernateBatchProcessingExample - Flush a batch of INSERT & release memory: 2 time(s) 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 11] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [12] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 12] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [13] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 13] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [14] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 14] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [15] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 15] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [16] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 16] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [17] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 17] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [18] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 18] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [19] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 19] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [20] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 20] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [21] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - select nextval ('tag_id_seq') 2020-Apr-02 16:30:48 PM [main] INFO  com.gpcoder.HibernateBatchProcessingExample - Flush the last time at commit time 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 21] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [22] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 22] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [23] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 23] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [24] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 24] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [25] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 25] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [26] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 26] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [27] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 27] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [28] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 28] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [29] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 29] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [30] 2020-Apr-02 16:30:48 PM [main] DEBUG org.hibernate.SQL - insert into Tag (name, id) values (?, ?) 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Processing 30] 2020-Apr-02 16:30:48 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [31] 2020-Apr-02 16:30:48 PM [main] INFO  org.hibernate.engine.internal.StatisticalLoggingSessionEventListener - Session Metrics {     22812 nanoseconds spent acquiring 1 JDBC connections;     21149 nanoseconds spent releasing 1 JDBC connections;     744689 nanoseconds spent preparing 33 JDBC statements;     4893934 nanoseconds spent executing 30 JDBC statements;     2470413 nanoseconds spent executing 3 JDBC batches;     0 nanoseconds spent performing 0 L2C puts;     0 nanoseconds spent performing 0 L2C hits;     0 nanoseconds spent performing 0 L2C misses;     32588030 nanoseconds spent executing 3 flushes (flushing a total of 30 entities and 0 collections);     0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections) } 2020-Apr-02 16:30:48 PM [main] INFO  org.hibernate.orm.connections.pooling - HHH10001008: Cleaning up connection pool [jdbc:postgresql://localhost:5432/gp_batch_processing] Process finished with exit code 0

Như bạn thấy, chúng ta Hibernate đã thực thi: 3 JDBC batches, 3 flushes, 30 entities.

Hibernate Batch Update (Session scroll)

Cũng giống như lúc INSERT, khi truy xuất, UPDATE dữ liệu hãy dùng các method  flush() và clear() để giải phóng bộ nhớ cache. Ngoài ra, sử dụng method scroll() để tận dụng các con trỏ ở server cho các truy vấn trả về nhiều dữ liệu.

package com.gpcoder; import com.gpcoder.entities.Tag; import com.gpcoder.utils.HibernateUtils; import lombok.extern.log4j.Log4j2; import org.hibernate.*; @Log4j2 public class HibernateBatchUpdateExample {     public static void main(String[] args) {         try (SessionFactory sessionFactory = HibernateUtils.getSessionFactory();              Session session = sessionFactory.openSession();) {             log.info("Statistics enabled = " + sessionFactory.getStatistics().isStatisticsEnabled());             session.beginTransaction();             final int batchSize = 10; // same as the JDBC batch size             try (ScrollableResults scrollableResults = session                     .createQuery( "FROM Tag" )                     .setCacheMode( CacheMode.IGNORE )                     .scroll( ScrollMode.FORWARD_ONLY );             ) {                 int count = 1;                 while ( scrollableResults.next() ) {                     Tag tag = (Tag) scrollableResults.get( 0 );                     tag.setName("Hibernate Batch Update " + count);                     if ( count % batchSize == 0 && !scrollableResults.isLast()) {                         log.info("Flush a batch of UPDATE & release memory: {} time(s)", (count / batchSize));                         session.flush();                         session.clear();                     }                     count++;                 }                 log.info("Flush the last time at commit time");             }             session.getTransaction().commit();         }     } }

Output:

2020-Apr-02 16:58:01 PM [main] INFO  org.hibernate.Version - HHH000412: Hibernate Core {5.4.7.Final} 2020-Apr-02 16:58:01 PM [main] INFO  org.hibernate.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.0.Final} 2020-Apr-02 16:58:01 PM [main] WARN  org.hibernate.orm.connections.pooling - HHH10001002: Using Hibernate built-in connection pool (not for production use!) 2020-Apr-02 16:58:01 PM [main] INFO  org.hibernate.orm.connections.pooling - HHH10001005: using driver [org.postgresql.Driver] at URL [jdbc:postgresql://localhost:5432/gp_batch_processing] 2020-Apr-02 16:58:01 PM [main] INFO  org.hibernate.orm.connections.pooling - HHH10001001: Connection properties: {user=postgres, password=****} 2020-Apr-02 16:58:01 PM [main] INFO  org.hibernate.orm.connections.pooling - HHH10001003: Autocommit mode: false 2020-Apr-02 16:58:01 PM [main] INFO  org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl - HHH000115: Hibernate connection pool size: 4 (min=1) 2020-Apr-02 16:58:01 PM [main] INFO  org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.PostgreSQL94Dialect 2020-Apr-02 16:58:02 PM [main] INFO  com.gpcoder.HibernateBatchUpdateExample - Statistics enabled = true 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - select tag0_.id as id1_1_, tag0_.name as name2_1_ from Tag tag0_ 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [2] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 0] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [3] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 1] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [4] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 2] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [5] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 3] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [6] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 4] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [7] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 5] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [8] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 6] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [9] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 7] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [10] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 8] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [11] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 9] 2020-Apr-02 16:58:02 PM [main] INFO  com.gpcoder.HibernateBatchUpdateExample - Flush a batch of UPDATE & release memory: 1 time(s) 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 1] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [2] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 2] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [3] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 3] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [4] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 4] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [5] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 5] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [6] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 6] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [7] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 7] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [8] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 8] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [9] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 9] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [10] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 10] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [11] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [12] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 10] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [13] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 11] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [14] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 12] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [15] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 13] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [16] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 14] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [17] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 15] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [18] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 16] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [19] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 17] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [20] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 18] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [21] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 19] 2020-Apr-02 16:58:02 PM [main] INFO  com.gpcoder.HibernateBatchUpdateExample - Flush a batch of UPDATE & release memory: 2 time(s) 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 11] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [12] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 12] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [13] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 13] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [14] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 14] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [15] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 15] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [16] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 16] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [17] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 17] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [18] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 18] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [19] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 19] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [20] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 20] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [21] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [22] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 20] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [23] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 21] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [24] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 22] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [25] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 23] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [26] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 24] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [27] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 25] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [28] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 26] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [29] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 27] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [30] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 28] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([id1_1_] : [BIGINT]) - [31] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - extracted value ([name2_1_] : [VARCHAR]) - [Hibernate Batch Processing 29] 2020-Apr-02 16:58:02 PM [main] INFO  com.gpcoder.HibernateBatchUpdateExample - Flush the last time at commit time 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 21] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [22] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 22] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [23] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 23] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [24] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 24] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [25] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 25] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [26] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 26] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [27] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 27] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [28] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 28] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [29] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 29] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [30] 2020-Apr-02 16:58:02 PM [main] DEBUG org.hibernate.SQL - update Tag set name=? where id=? 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Hibernate Batch Update 30] 2020-Apr-02 16:58:02 PM [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [31] 2020-Apr-02 16:58:02 PM [main] INFO  org.hibernate.engine.internal.StatisticalLoggingSessionEventListener - Session Metrics {     22833 nanoseconds spent acquiring 1 JDBC connections;     22036 nanoseconds spent releasing 1 JDBC connections;     218128 nanoseconds spent preparing 4 JDBC statements;     685155 nanoseconds spent executing 1 JDBC statements;     4171650 nanoseconds spent executing 3 JDBC batches;     0 nanoseconds spent performing 0 L2C puts;     0 nanoseconds spent performing 0 L2C hits;     0 nanoseconds spent performing 0 L2C misses;     44981036 nanoseconds spent executing 3 flushes (flushing a total of 30 entities and 0 collections);     37173 nanoseconds spent executing 1 partial-flushes (flushing a total of 0 entities and 0 collections) } 2020-Apr-02 16:58:02 PM [main] INFO  org.hibernate.orm.connections.pooling - HHH10001008: Cleaning up connection pool [jdbc:postgresql://localhost:5432/gp_batch_processing] Process finished with exit code 0

Stateless Session

Trái với Session, StatelessSession sẽ không cache lại ở bất cứ mức nào: first-level cache, second-level hay query cache. Nó cũng không gắn với một Persistence Context nào. Vì vậy sử dụng cách này sẽ không sợ cache làm OutOfMemory.

Chi tiết các bạn tham khảo thêm Hibernate StatelessSession document.

Hibernate Query Language for DML

DML (Data Manipulation Language – Ngôn ngữ thao tác dữ liệu) đề cập đến các câu lệnh SQL như INSERT, UPDATE và DELETE. Hibernate cung cấp các phương thức để thực thi số lượng lớn câu lệnh DML dưới dạng Ngôn ngữ truy vấn Hibernate (HQL). Bằng cách này chúng ta sẽ gọi trực tiếp vào database nên không gặp vấn đề OutOfMemory.

Chi tiết các bạn tham khảo thêm Hibernate Query Language for DML document.

Tài liệu tham khảo:

Bài viết gốc được đăng tải tại gpcoder.com

Có thể bạn quan tâm:

Xem thêm Việc làm Developer hấp dẫn trên TopDev

Top 5 câu hỏi phỏng vấn java hay nhất

câu hỏi phỏng vấn java

Bài viết được sự cho phép của tác giả Kiên Nguyễn

Hôm nay ngồi viết bài, đang trong thời kì nhà nhà nhảy việc, người người nhảy việc. Chính vì lý do đó, thiết nghĩ nên viết một bài lựa ra một số câu hỏi phỏng vấn java hay giúp đỡ anh em.

Cố gắng tu luyện để đạt tới mức độ như này!. Nguồn / Source: pinterest.com

Thật tình mà nói, 5 câu hỏi này không phải lúc nào bắt đầu phỏng vấn cũng hỏi. Thường thì người ta chạy roda vài câu đơn giản trước. Chả ai hỏi từ khóa ít dùng như thế này. Ví dụ như: Abstract là gì?Interface là gì?Sự khác biệt giữa Linked List và Array List?.

Thiệt tình là thế, nhưng nếu mấy ông đi phỏng vấn gặp ngay một ông anh khá cứng. Câu hỏi đâu tiên tất nhiên sẽ không phải như thế. Nên hãy tham khảo 5 câu hỏi dưới đây nhé:

Ở phía dưới câu hỏi là phần trả lời của mình, nếu có chỗ nào chưa chính xác các bạn vui lòng comment ở phía dưới.

Tuyển Java lương cao làm việc online

  10 Java Web Framework tốt nhất
  10 lý do cho thấy tại sao bạn nên theo học ngôn ngữ lập trình Java

1. Từ khóa volatile là gì?. Tại sao ta cần sử dụng chúng?

Ghê, câu hỏi phỏng vấn Java đầu tiên mà đụng tới Volatile thì thường vị trí apply cũng là Senior rồi đấy!. Quay lại câu hỏi, cần nhớ rằng trong Java, mỗi thread (luồng) sẽ có một ngăn xếp (stack) được cấp phát riêng. Ngăn xếp có gì?

Ngăn xếp (stack) này chứa bản sao các biến (copy of variables) có thể truy cập.

Khi luồng được khởi tạo, nó sao chép tất cả các biến được phép truy cập vào luồng của nó. Lúc này cần tới từ khóa Volatile.

Volatile nói “Hey, JVM. Warning, this variable may be modified in another Thread

Này JVM, hãy cẩn thận, biến này có thể được thay đổi ở một luồng khác đấy!

Chốt lại, từ khóa volatile đảm bảo rằng khi đọc và ghi vào một biến, giá trị của biến sẽ luôn được cập nhật và là global.

Từ khóa Volatile luôn đảm bảo giá trị của variable sẽ được lấy từ Main Memory
Nguồn / Source: knpcode.com

This implies that every thread accessing a volatile field will read the variable’s current value instead of (potentially) using a cached value.

Điều này ngụ ý rằng tất cả các luồng truy cập tới volatile field đều sẽ đọc được giá trị hiện tại của biến (thay vì các gía trị có thể được lưu trong bộ nhớ cache)

Nếu interviewer yêu cầu cho ví dụ:

public class KieBlog extends Thread {
private volatile boolean close = false;
public void run() {
while(!close) {
// do work
}
}
public void close() {
close = true;
// interrupt here if needed
}
}

Ví dụ này phổ biến nhất, Thread KieBlog sẽ kết thúc khi cờ close bật là true, việc sử dụng Volatile sẽ đảm bảo giá trị biến close luôn được đọc từ luồng chính (main thread), không cần phải sử dụng từ khóa synchronized.

Nếu trong job description có nhắc tới thread, multi thread thì các bạn xem thêm các câu hỏi phỏng vấn java ở cuối bài nhé.

2. sleep() và wait() khác nhau như thế nào?

Sleep() là một hoạt động giữ monitor, khóa đối tượng được chia sẻ (lock of the shared object) trong số milisecond được chỉ định.

Sleep() thì chỉ cần trả lời ngắn gọn như vậy là được. Nhưng tới wait() thì các bạn lưu ý dùm là:

Wait(), pauses the thread until either the specified number of milliseconds have elapsed or it receives a desired notification from another thread (whichever is first)

Wait() thì chỉ cần tạm dừng một luồng trong số miliseconds được chỉ định HOẶC nhận được một thông báo từ luồng khác (tùy theo cái nào đến trước)

Sleep() thường được sử dụng phổ biến để kiểm tra một số kết quả nhất định. Chờ một kết quả khác đang trong quá trình thực thi.

Wait() thì lại thường được sử dụng trong các application đa luồng. Dùng kèm với notify() và notifyAll(). Nhằm đảm bảo tính đồng bộ hóa (synchronization) trên tất cả các thread.

Tham khảo thêm sự khác biệt giữa sleep() và wait() trong Java

3. Có hai thread đang chạy là 1 và 2. Thread 1 throw exception, thread 2 bắt như thế nào?

Để trả lời được câu này thì trước tiên phải nắm chắc về throw exception. Exception thường không phải là câu hỏi phỏng vấn java hay gặp. Tuy nhiên, nắm chắc thì càng tốt, có thể đọc bài viết về Java Exception ở KieBlog. Đây cũng được đánh giá là một câu hỏi phỏng vấn lạ cho những ai ít lập trình đa luồng (multi thread).

Để catch được exception ở thread khác, ta có thể sử dụng Thread.UncaughtExceptionHandler. Xem xét ví dụ dưới đây:

// Khởi tạo handle
Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread th, Throwable ex) {
System.out.println("Uncaught exception: " + ex);
}
};

// Tạo thread thứ 2, tất nhiên là new Thread
Thread threadTwo = new Thread() {
public void run() {
System.out.println("Tạm dừng thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Dừng.");
}
System.out.println("Throwing exception ...");
throw new RuntimeException();
}
};

// Set uncaught exception, cần có argument handler
// ném ra an uncaught exception
threadTwo.setUncaughtExceptionHandler(handler);

// Tới đây, exception được ném ra ở threadTwo sẽ được bắt lại thông qua handler
threadTwo.start();

4. Method reference là gì?. Tiện lợi như nào?

Kể từ sau Java 8, đây cũng là một câu hỏi phỏng vấn java khá hay. Rất có thể anh em đã dùng nhiều, nhưng để trả lời cho chuẩn chỉnh thì chưa chắc đã trả lời được.

Method reference được giới thiệu ở Java 8 cho phép ta khởi tạo cái constructors và các methods (static hoặc không). Quan trọng là các methods hay constructors này có thể sử dụng như là lambdas.

// Interview service
// Sai một câu thì trướt =)))
final InterviewService service = new InterviewService();
service.start();

onFailAnswer(new Runnable() {
@Override
public void run() {
service.stop();
}
});

Với lambdas, chả cần phải viết dài dòng văn tự như vậy

// Easy for lambdas
onFailAnswer(() -> service.stop());
cau-hoi-phong-van-java-method-referenceVí dụ về Method reference. Nguồn/ Source: baeldung.com

Nhắc tới reference method chắc chắn phải nhắc tới Stream, qủy Stream sử dụng rất linh hoạt như này:

// Lấy ra toàn bộ title các câu hỏi phỏng vấn
// Reference ở chỗ method gọi sau ::
List<InterviewQuestion> itvQuestion = ...

List<String> questionTitle = itvQuestion.stream()
.map(InterviewQuestion::getQuestionTitle)
.map(String::toLowerCase)
.collect(toList());

5. Sự khác biệt giữa Jar và war

Ẹc, câu này cũng độc, chưa chắc là ai cũng trả lời đúng nhé.

JAR gần giống như nghĩa của nó, cái hũ:

  • Full form của các file Java.
  • Một file JAR có thể có nhiều file Java.
  • JAR thường được sử dụng để lưu giữ library.

Còn WAR thì sao, WAR lại không gần giống như nghĩa chiến tranh:

  • Full form của WAR là Web Archive Files.
  • WAR chứa đủ thứ: XML, Java Servlet page, …
  • Chủ yếu sử dụng cho ứng dụng web.

Câu này giống như hỏi troll ấy nhở?

6. Tham khảo thêm các câu hỏi phỏng vấn java

Phù, cuối cùng cũng hêt 5 câu hỏi mình muốn chia sẻ. Các bạn trả lời được mấy câu?.

Chưa đúng thì tìm hiểu thêm nha, chúc may mắn!

Kể từ sau khi Stream được giới thiệu trong Java 8, Stream chắc chắn sẽ có trong danh sách các câu hỏi phỏng vấn java. Để nắm chắc, tự tin khi phỏng vấn Stream, các ông có thể đọc bài Tuốt tuồn tuột về Stream trong Java 8 ở Kieblog. Đọc xong thì trả lời tuốt tuồn tuột luôn.

Tất nhiên học không phải là chỉ để phỏng vấn, nhớ cho mình nữa ha!

Ngoài ra, để chuẩn bị thật tốt, hãy tham khảo thêm một số nguồn sau:

Bài viết gốc được đăng tải tại kieblog.vn

Có thể bạn quan tâm:

Xem thêm Việc làm cntt hấp dẫn trên TopDev

Giới thiệu về OAuth

Giới thiệu về OAuth

Bài viết được sự cho phép của tác giả Tino Phạm

1. OAuth là gì?

OAuth là một cách xác thực mở (open standard for authorization). Nếu bạn thấy một website (hay phần mềm) nào đó cho phép đăng nhập bằng tài khoản Facebook hay Google mà không cần tạo nick mới thì rất có khả năng đó chính là OAuth. Đây là một chuẩn xác thực mở được rất nhiều các website và phần mềm sử dụng.

Capture

Ví dụ về OAuth

Ví dụ khi bạn sử dụng Mediafire để lưu trữ dữ liệu. Khi lần đầu vào website mediafire, bên cạch việc tạo nick mới để login vào website, bạn cũng có thể login bằng tải khoản Facebook hoặc Twitter. Đó chính là bạn đang sử dụng OAuth. So với cách tạo nick mới rồi đăng nhập thì OAuth an toàn hơn vì nếu lỡ Mediafire bị hacker tấn công thì thông tin của bạn sẽ không bị đánh cắp, tài khoản Facebook hoặc Twitter của bạn cũng được an toàn.

  Building Microservices Application - Phần 3: Xác thực API bằng Oauth 2.0

  OAuth2 là gì? Tìm hiểu về OAuth2

Nếu bạn làm theo cách cũ, tức là đăng ký nick Mediafire mới rồi đăng nhập, tức là Mediafire sẽ lưu trữ tất cả thông tin cá nhân của bạn, trong đó quan trọng nhất là email (Username) và mật khẩu. Mediafire bị hack đồng nghĩa với việc các thông tin này cũng rơi vào tay hacker. Còn khi dùng OAuth thì Mediafire sẽ không có được các thông tin cá nhân trên. Khi bạn đăng nhập bằng tài khoản Facebook/Twitter thì cái mà Mediafire nhận được từ Facebook/Twitter chỉ là một cái chìa khóa (token) chứa một số quyền hạn nhất định (không bao gồm quyền truy cập đến thông tin Username và Password). Cho nên nếu bị tấn công thì hacker chỉ lấy được những chìa khóa gần như vô dụng trên, còn Username và Password thì vẫn an toàn.

2. Phân biệt hai loại OAuth

Như vậy chúng ta có thể hiểu có 3 bên: Tôi, bạn và Facebook/Twitter, OAuth chính là 1 cách làm “ngọt ngào” để 3 bên nói chuyện với nhau một cách mềm dẻo, sao cho bạn có thể trao cho tôi quyền được truy cập vào các tài nguyên của bạn trên Facebook/Twitter
  • 2-legged OAuth: là kiểu Authorization trong đó vai trò của bạn và Client là như nhau. Tức là Client chính là bạn và Server. Đó là kịch bản Client-Server thông thường.
  • 3-legged OAuth: là kiểu Authorization trong đó Bạn và Client là phân biệt. Client muốn bạn chia sẻ tài nguyên đã có bên phía Server.

3. Cách OAuth hoạt động

A. 2- legged OAuth
  1. Client đăng ký sử dụng dịch vụ với Server
  2. Server cho Client
    • CONSUMER_KEY
    • CONSUMER_SECRET_KEY
  3. Client sử dụng các keys trên để thực hiện Authorization
B. 3- legged OAuth
  1. Client đăng ký sử dụng dịch vụ với Server
  2. Server cho Client
    • CONSUMER_KEY
    • CONSUMER_SECRET_KEY
  3. Bạn có tài nguyên ở Server
  4. Bạn sử dụng dịch vụ ở Client, Client yêu cầu bạn cho phép khai thác tài nguyên của bạn ở Server
  5. Client yêu cầu Server gửi REQUEST_TOKEN cho bạn
  6. Client chuyển bạn đến Server Authentication
  7. Bạn đăng nhập vào Server, Server hỏi bạn có muốn chia sẻ quyền khai thác dữ liệu cho Client hay không
  8. You đồng ý, Server chuyển You về Client kèm theo ACCESS_TOKEN
  9.  Client sử dụng ACCESS_TOKEN để thực hiện thao tác trên các tài nguyên của You thuộc Server

3. Ưu điểm – Nhược điểm

  • Tiện lợi, nhanh chóng: không cần phải tạo nick mới mỗi khi đăng nhập vào một website hay phần mềm.
  • An toàn: Hạn chế được tình trạng đánh cắp mật khẩu, thông tin cá nhân.
  • OAuth 2.0 khắc phục được các lỗi bảo mật so với các phiên bản trước. Mặc dù vẫn còn tồn tại lỗ hỏng bảo mật khi duyệt web ở chrome . nhưng người dùng có thể hạn chế bằng cách sử dụng HTTPS thay thì HTTP thông thường.
  • Bạn có thể đã nghe cụm từ “Đừng để tất cả trứng vào trong một giỏ” (Don’t keep all your eggs in one basket) bạn đã có thể đọc trực tuyến ít nhất một lần, do vậy không nên sử dụng cùng một mật khẩu cho tất cả các trang web bạn đăng nhập vào. Khi Facebook, Google, Twitter trở thành một điểm duy nhất nếu có tình huống sảy ra . Nếu bạn mất quyền truy cập vào bất kỳ các tài khoản, bạn sẽ mất quyền truy cập vào tất cả các trang web dựa trên tài khoản đó.

Tham khảo:

Wikipedia

DatGS’s Blog

Tinhte.vn

Bài viết gốc được đăng tải tại thangphampt.wordpress.com

Có thể bạn quan tâm:

Xem thêm Việc làm Developer hấp dẫn trên TopDev

Hướng dẫn viết Theme WordPress toàn tập phần 2

Hướng dẫn viết Theme WordPress toàn tập phần 2

Bài viết được sự cho phép của tác giả Trần Anh Tuấn

Ở bài trước chúng ta đã setup mọi thứ và cũng xem qua Design luôn rồi. Hôm nay chúng ta sẽ bắt đầu viết theme WordPress và phần đầu tiên đó chính là Header và Banner của Design.

hướng dẫn viết theme wordpress chi tiết

Các hình ảnh-icons trong Design mình đã lấy toàn bộ và upload lên Drive các bạn có thể nhấn vào đây để tải về để sử dụng trong quá trình viết theme WordPress nhé.

# Setup cơ bản

Trước khi đi vào công việc chính thì các bạn cần cài một số Plugins sau để hỗ trợ cho việc viết theme WordPress nhé. Những plugins đó là Advanced Custom FieldsCustom Post Type UISVG Support.

Tiếp theo các bạn mở file style.css lên các bạn xóa hết code đi, chỉ để lại phần ghi chú thôi sau đó vào trang này copy đoạn code reset CSS rồi dán vào file style.css là được. Lúc này file style.css sẽ trông như thế này. Về reset CSS là gì các bạn có thể tìm hiểu nó tại đây nha.

/*!
Theme Name: Maker
Theme URI: http://underscores.me/
Author: Underscores.me
Author URI: http://underscores.me/
Description: Description
Version: 1.0.0
......
*/
/*reset css below*/
reset css paste here

Tiếp theo các bạn mở file functions.php lên và copy các dòng dưới đây dán vào dưới cùng. Những đoạn code này có chức năng cho phép chúng ta thêm class vào vào thẻ li, hay thẻ a trong Menu mà tí nữa chúng ta sẽ làm thông qua hàm wp_nav_menu.

/*Adding class to menu item - li tag */
function add_menu_list_item_class($classes, $item, $args) {
if($args->list_item_class) {
$classes[] = $args->list_item_class;
}
return $classes;
}
add_filter('nav_menu_css_class', 'add_menu_list_item_class', 1, 3);

/*Adding class to link menu item - a tag */
function add_menu_link_class( $atts, $item, $args ) {
if($args->link_class) {
$atts['class'] = $args->link_class;
}
return $atts;
}
add_filter( 'nav_menu_link_attributes', 'add_menu_link_class', 1, 3 );
Một lưu ý nữa là sau khi các bạn cài đặt xong các Plugins rồi thì đừng quên vào Admin Dashboard chọn Thư viện sau đó nhấn Tải Lên -> Chọn tập tin -> Chọn hết toàn bộ hình ảnh các bạn đã tải về khi nãy rồi upload lên thôi.

# Header

Như trong Design thì chúng ta thấy có một Logo bên trái và một Menu ở bên phải. Logo mặc định WordPress có cung cấp ở mục Tùy biến(Customize) nhưng làm sao để lấy nó ra và hiển thị ở vị trí mà ta mong muốn thì mình sẽ hướng dẫn sau. Còn Menu thì rất đơn giản, các bạn vào Dashboard -> Giao diện -> Menu

Các bạn sẽ thấy giao diện có một Menu có tên là Menu 1. Các bạn thêm các Item vào Menu này cho nó đủ bộ như Design luôn nhé, chọn Liên kết tự tạo rồi nhấn Thêm vào menu. Đừng quên tích Primary và nhấn Lưu menu nhé

Bây giờ các bạn mở file header.php lên sẽ thấy rất nhiều code phức tạp mà lúc chúng ta tải về nó đã có sẵn, các bạn xóa sạch từ khúc <div id="page" class="site"> trở xuống luôn nha sau đó các bạn dán vào đoạn code của mình dưới đây

<header class="header">
 <div class="container header__container">
  <?php 
    $custom_logo_id = get_theme_mod( 'custom_logo' );
    $image = wp_get_attachment_image_src( $custom_logo_id , 'full' );
  ?>
  <a href="<?php echo esc_url( home_url( '/' ) ); ?>" class="logo">
   <img src="<?php echo $image[0];?>" alt="">
  </a>
 <?php
  wp_nav_menu( array(
   'theme_location'  => 'menu-1',
   'container'    => 'nav',
   'container_class'  => 'menu',
   'menu_class'    => 'menu__list',
   'link_class'        => 'menu__link',
   'list_item_class'   => 'menu__item'
  ) );
 ?>
 </div>
</header>

Đoạn code $custom_logo_id sẽ lấy ra ID của Logo mặc định của website mà mình đã nói khi nãy. Sau đó mình dùng hàm wp_get_attachment_image_src để lấy ra thông tin của hình logo đó thông qua ID của nó

Và $image sẽ trả về một mảng gồm nhiều thông tin và ở phần tử đầu tiên tức là 0 trong mảng là đường dẫn hình ảnh mà chúng ta cần mình cho nó vào thẻ img.

Đoạn esc_url( home_url( '/' ) ) nó sẽ in ra địa chỉ trang chủ của website chúng ta để khi người ta nhấn vào logo sẽ quay về trang chủ.

Hàm wp_nav_menu các bạn có thể tìm hiểu kỹ hơn tại đây. Chức năng của nó là lấy ra Menu mà chúng ta đã tạo. Khi các bạn tải từ Underscore về mặc định sẽ có một cái Menu tên là Menu 1 và có slug tương ứng là menu-1

Chỗ theme_location sẽ lấy ra menu theo id, slug hoặc tên mà chúng ta muốn, ở đây là Menu có slug là menu-1, container là thẻ nav bọc ngoài Menu, container_class là class của thẻ nav ở đây là menu nghĩa là lúc chạy code nó sẽ sinh ra <nav class="menu"....> như thế này

menu_class là class dành cho thẻ ul bên trong thẻ nav, riêng link_class và list_item_class thì mặc định không có nên chúng ta phải code thêm vào functions.php mà mình đã đề cập ở đoạn mình nói các bạn copy code vào dưới cùng file functions.php đấy.

Lúc này giao diện vẫn chưa có gì ngoài cái Menu gồm các links thô sơ mà chúng ta đã tạo và Logo chưa hiện ra do chúng ta chưa tùy biến. Tiếp theo đây chúng ta sẽ setup và bắt đầu code CSS cho nó đẹp ra nhé.

  10 theme Sublime Text tốt nhất
  Hướng dẫn lập trình theme woocommerce từ a đến z

# style.css

Về phần CSS thì tạm thời mình sẽ dùng biến cho các mã màu để dễ dàng thay đổi và tùy chỉnh về sau. Lưu ý là biến trong CSS không hỗ trợ IE nếu sau này các bạn cần hỗ trợ cho IE thì vào thay thế biến bằng mã màu lại là được rồi.

Đơn vị mình sử dụng sẽ là REM và PX, Font Family của Design này là Lato cho nên mình sẽ lên Google Fonts lấy về rồi chèn vào thẻ head trong file header.php. Mình code chay không dùng thư viện gì cả.

Mình setup một số biến cho mã màu như chủ đạo, màu chữ cũng như là font-family cho website. Và mình thấy nội dung nằm ở giữa có độ rộng là 1185px được dùng đi dùng lại nhiều cho nên mình đặt một class có tên là .container để tái sử dụng.

/*************Main Styles **************/
:root {
 --first: #2583FD;
 --second: #07E1A2;
 --gray: #9299A6;
 --gray-dark: #2C3340;
 --gray-light: #AFB5C2;
}
html {
 font-size: 62.5%;
}
body {
 font-family: "Lato", sans-serif;
}
.container {
 max-width: 1185px;
 margin: 0 auto;
}
.header {
 background-color: white;
 padding-top: 1rem;
 padding-bottom: 1rem;
}
.header__container {
 display: flex;
 align-items: center;
 justify-content: space-between;
}
.menu__list {
 display: flex;
 align-items: center;
 font-size: 1.4rem;
}
.menu__item:not(:last-child) {
 margin-right: 1rem ;
}
.menu__link {
 display: block;
 padding: 1.5rem 2rem;
 color: var(--gray-dark);
 text-decoration: none;
 transition: color .2s linear;
}
.menu__link:hover {
 color: var(--first);
}
.menu__item:last-child .menu__link {
 border: 1px solid var(--gray);
 border-radius: 4px;
}

Tạm thời chúng ta được kết quả như hình

Ây chà chà cái logo vẫn chưa có. Đừng lo lắng, các bạn nhấn vào Giao diện -> Tùy biến -> chọn Nhận dạng Site  -> Chọn logo -> Tải tập tin lên -> Chọn file logo-dark -> Upload lên -> Sau đó nhấn chọn và Bỏ cắt -> Nhấn Đăng là xong.

Các bạn sẽ được kết quả như hình. Thế là chúng ta đã làm xong phần Menu. Tiếp theo dưới đây là phần phức tạp hơn một chút đó là Banner vì có kết hợp với một Plugin mà chúng ta đã cài từ lúc đầu đó chính là Advanced Custom Fields.

# Banner

Giao diện Banner nhìn vào thì dễ dàng ta thấy có những phần linh động thay đổi như một tấm hình bên phải, hình nền dưới lượn sóng dưới cùng, một tiêu đề nhỏ phía trên rồi đến một tiêu đề to, một đoạn text nhỏ dài và một cái hình App store kèm liên kết khi nhấn vào.

Ngoài ra còn có 2 hình tròn mờ nữa nhưng nó có thể làm bằng CSS nên chúng ta không cần phải tạo fields, đơn giản chỉ thêm hai class cho chúng mà thôi.

Như vậy tổng cộng có 7 chỗ có thể dễ dàng thay đổi như vậy chúng ta cần tạo một nhóm Fields dành cho Banner, và để tạo Custom Fields trong WordPress thì plugin hỗ trợ mạnh mẽ nhất đó chính là Advanced Custom Fields.

Trước khi đi vào việc tạo Custom Fields thì việc chúng ta cần làm trước đó là tạo một file tên là content-banner.php trong thư mục gốc để code cho phần Banner. Tiếp đến các bạn tạo một file tên là page-home.php rồi dán code dưới đây vào

<?php
/*
  Template Name: Home Page  
*/
get_header();
?>
<main>
  <?php get_template_part('content','banner'); ?>
</main>
<?php
get_footer();

Sau đó các bạn vào Dashboard chọn Trang -> Thêm trang mới với tên là Home. Ở phần Giao diện các bạn chọn là Home Page -> Đăng.

Rồi các bạn vào Cài đặt chọn Đọc và chỗ Trang chủ các bạn chọn Home(page vừa tạo ở trên) và nhấn Lưu thay đổi

Tiếp đến chúng ta chèn nội dung file content-banner vào file page-home.php bằng hàm get_template_part (đã có ở đoạn code trên). Sau đó các bạn mở file content-banner.php lên và tạm thời code HTML như sau:

<section class="banner">
<div class="container banner__container">
 <div class="banner__content">
  <span class="heading__small">Introducing maker</span>
  <h2 class="heading__">Simple yet powerful webflow template for your startup</h2>
  <p class="desc">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Laudantium reprehenderit dolorum minus recusandae sapiente provident. Animi quaerat velit, mollitia nisi inventore voluptatum non ullam quibusdam ducimus iusto voluptatem incidunt nulla.</p>
  <a href="#" class="download__app-store">
    <img src="" alt="">
  </a>
 </div>
 <div class="banner__image">
  <img src="" alt="">
 </div>
 </div>
<div class="banner__circle banner__circle--small"></div>
<div class="banner__circle banner__circle--big"></div>
<div class="banner__waves"></div>
</section>

Trên đây chỉ là dữ liệu giả mình nhập sẵn mà thôi, nhưng chúng ta muốn nó linh động có thể dễ dàng thay đổi, cập nhật và lưu ở trong WordPress. Để làm điều đó chúng ta phải tạo Custom Fields.

# ACF

Để tạo Custom Fields các bạn chọn vào phần Custom Fields trong Dashboard sau đó chọn Add new để thêm mới, ở giao diện hiện ra các bạn điền vào tiêu đề Custom Fields ở đây mình để là Banner

Như đã phân tích ở trên tổng cộng chúng ta có 7 Fields bao gồm 4 Fields Text và 3 Fields Image. Để tạo Field Text, các bạn nhấn vào Add Field nó sẽ hiện ra như hình dưới đây. Các bạn điền vào như mình nhé.

Tương tự các bạn tạo thêm ba Fields khác với cấu trúc như hình trên nhưng Field Label khác là được, còn Field Name nó sẽ tự động lấy theo Field Label khi các bạn nhập vào. Các bạn làm giống như mình dưới đây

Còn Fields Image thì khác ở chỗ chúng ta sẽ chọn Field Type là Image và chọn kết quả trả về là Image Array

Tương tự các bạn tạo cho hai Fields cho hình còn lại như mình dưới đây nhé.

Tiếp đến là phần hiển thị những Custom Fields này ở trang nào, tất nhiên là trang Home rồi. Các bạn để ý phần Location, các bạn thiết lập theo như hình dưới đây nhá. Sau đó đừng quên nhấn nút Lưu phía trên bên phải nhé.

Sau đó các bạn quay về trang Home mà các bạn đã tạo khi nãy, các bạn sẽ thấy các Custom Fields nằm trong nhóm Banner mà chúng ta đã tạo như hình.

Thế là xong bước dùng ACF đơn giản, tiếp theo đây là cách làm sao để khi chúng ta cập nhật vào những Fields đó thì nó sẽ lưu lại và hiển thị ra bên ngoài.

# Cách lấy dữ liệu từ ACF

Để lấy dữ liệu từ ACF ra và hiển thị trong code thì chúng ta sẽ gọi chúng ra thông qua hàm get_field("tên field) mà ACF cung cấp, ở đây tên field là cái tên mà lúc chúng ta tạo trong ACF(Field Name chứ không phải Field Label nha các bạn)

Bây giờ mình sẽ áp dụng chúng vào trong file content-banner.php luôn nhé. Các bạn mở file content-banner.php lên và chèn đoạn code dưới đây vào trên cùng.

<?php
$banner_small_title = get_field('banner_small_title');
$banner_big_title = get_field('banner_big_title');
$banner_desc = get_field('banner_desc');
$banner_app_download_link = get_field('banner_app_download_link');
$banner_app_download = get_field('banner_app_download');
$banner_image = get_field('banner_image');
$banner_background_bottom = get_field('banner_background_bottom');
?>

Sau đó chúng ta chỉ việc gọi nó ra mà thôi. Ta có toàn bộ code ở file content-banner.php như sau:

<?php
$banner_small_title = get_field('banner_small_title');
$banner_big_title = get_field('banner_big_title');
$banner_desc = get_field('banner_desc');
$banner_app_download_link = get_field('banner_app_download_link');
$banner_app_download = get_field('banner_app_download');
$banner_image = get_field('banner_image');
$banner_background_bottom = get_field('banner_background_bottom');
?>
<section class="banner">
 <div class="container banner__container">
  <div class="banner__content">
   <span class="heading__small"><?php echo $banner_small_title ;?></span>
   <h2 class="heading__big"><?php echo $banner_big_title ;?></h2>
   <p class="desc"><?php echo $banner_desc ;?></p>
   <a href="<?php echo $banner_app_download_link ;?>" class="download__app-store">
     <img src="<?php echo $banner_app_download['url'] ;?>" alt="<?php echo $banner_app_download['alt'] ;?>">
   </a>
  </div>
  <div class="banner__image">
   <img src="<?php echo $banner_image['url'] ;?>" alt="<?php echo $banner_image['alt'] ;?>">
  </div>
 </div>
 <div class="banner__circle banner__circle--small"></div>
 <div class="banner__circle banner__circle--big"></div>
 <div class="banner__waves" style="background-image:url(<?php echo $banner_background_bottom['url'] ;?>);"></div>
</section>

Hiện tại chưa có gì vì chúng ta chưa thêm gì ở trang Home cả. Giờ mở trang Home lên sau đó tại Banner, các bạn nhập đại các thông tin vào thử xem. Mình nhập vào như này cho giống Design luôn

Tada. Chúng ta được kết quả như mong đợi nhé.

Nhưng mà chưa đẹp cho lắm vì chúng ta chưa CSS cho nó nhỉ. Và đây là đoạn CSS sẽ làm cho nó đẹp lên, các bạn có thể tham khảo của mình hoặc tự code nhé. Làm xong các bạn reload lại xem kết quả có đẹp không nhé.

.banner {
 background-image: linear-gradient(to right bottom, var(--first), var(--second));
 position: relative;
 padding-top: 8rem;
 overflow: hidden;
}

.banner__container {
 display: flex;
 align-items: center;
 justify-content: space-between;
 position: relative;
 z-index: 2;
}

.banner__content {
 max-width: 47rem;
 color: white;
}

.banner__image img {
 max-width: 42rem;
}

.banner__circle {
 position: absolute;
 border-style: solid;
 border-color: white;
 border-radius: 50%;
 opacity: 0.06;
}

.banner__circle--small {
 border-width: 8vh;
 width: 44vh;
 height: 44vh;
 top: 10rem;
 left: 8rem;
}

.banner__circle--big {
 border-width: 14vh;
 width: 110vh;
 height: 110vh;
 bottom: -50%;
 right: -5%;
}

.banner__waves {
 position: absolute;
 left: 0px;
 right: 0px;
 bottom: -1px;
 z-index: 3;
 height: 8vw;
 background-position: 50% 100%;
 background-size: cover;
 background-repeat: no-repeat;
}

.heading__small {
 margin-bottom: 1.5rem;
 font-size: 1.1rem;
 line-height: 1.6;
 font-weight: 700;
 letter-spacing: 2px;
 text-transform: uppercase;
 display: inline-block;
}

.banner__content .heading__small {
 color: rgba(255, 255, 255, 0.6);
}

.heading__big {
 margin-bottom: 1.6rem;
 font-size: 3rem;
 line-height: 1.4;
 font-weight: 700;
}

.banner__content .heading__big {
 font-size: 4.4rem;
}

.desc {
 margin-bottom: 2.5rem;
 font-size: 1.4rem;
 line-height: 1.6;
}

.download__app-store {
 display: inline-block;
 background-color: white;
 border-radius: 4px;
}

# Tạm kết

Phù!!! Thế là xong phần 2. Cũng sắp tết luôn rồi. Hướng dẫn viết Theme cực lắm các bạn ạ, lỡ phóng lao rồi phải theo lao thôi kaka. Code rất nhiều và tùy chỉnh cũng rất nhiều nên nhiều khi không biết viết sao cho các bạn dễ hiểu để làm được.Nhưng thôi cứ cố gắng, hi vọng được các bạn ủng hộ.

Chúc các bạn ăn tết vui vẻ và học tập tốt nhé. Có gì không hiểu hoặc thấy mình làm sai chỗ nào đó hay thắc mắc đừng quên bình luận góp ý giúp mình nha. Ngoài ra mình có upload source code lên Github, các bạn muốn tham khảo đối chiếu thì nhấn vào đây nhé..

Bài viết gốc được đăng tải tại evondev.com

Có thể bạn quan tâm:

Xem thêm Tuyển dụng it wordpress hấp dẫn trên TopDev

Dynamic SQL và Static SQL – những điều cần biết

Dynamic SQL và Static SQL – những điều cần biết

Bài viết được sự cho phép của tác giả Kiên Nguyễn

Static SQL là gì?. Dynamic SQL là gì?. Nguồn gốc ra đời thật là một câu chuyện dài.

Anh em chúng ta ngày xưa xem phim chưởng Hongkong tất nhiên không thể không biết tới Trương Vô Kị, Tạ Tốn và BIG DICK môn phái. Ý nhầm (BIG SIX môn phái).

Ngày ấy, lúc Triệu Mẫn thách đấu Trương Tam Phong, trong lúc nguy khốn, Vô Kỵ được sư ông chỉ dạy cho một bộ quyền pháp có tên “Thái cực quyền”. Bộ võ học tuyệt tác này có đoạn viết:

Thái cực quyền trọng kì nghĩa, bất trọng kì chiêu.

Lấy TĨNH CHẾ ĐỘNG, lấy âm chế dương, lấy vô chiêu thắng hữu chiêu.

Tại sao tôi lại nhắc về thái cực quyền, vì bản thân cái Dynamic đã có nghĩa là động. Dynamic SQL là SQL động. Nhưng để hiểu được động thì tất nhiên phải biết một chút tới tĩnh.

Đm, thằng quỷ viết bài này!. Nói té đi là Dynamic, dẫn chuyện dài dòng. Bày đặt Thái Cực Quyền các kiểu, bố m vả cái lại chả răng rụng dynamic bây giờ.

Ấy chết, anh đừng nóng, viết vậy là vì sợ một số anh em chỉ chăm chăm vào Dynamic SQL mà quên vẫn còn SQL tĩnh sừng sững xưa giờ đó. Bắt đầu ngay đây!

Xem các vị trí tuyển dụng lập trình viên SQL lương cao cho bạn

1 SQL tĩnh – Static SQL – Minh giáo

Đầu tiên, định nghĩa rõ ràng của Static SQL:

Static or Embedded SQL are SQL statements in an application that do not change at runtime , therefore, can be hard-coded into the application

SQL tĩnh là các câu SQL trong application, không thể thay đổi lúc thực thi. Do đó, có thể set cứng các câu SQL trong ứng dụng

1.1 Điểm mạnh

Tất nhiên, SQL Tĩnh (Static SQL) không phải là không có lợi thế của nó. Các ứng dụng (application) sử dụng Static SQL sẽ có hiệu năng tốt hơn so với Dynamic SQL

Dynamic statements there’s no need for pre-compilation or re-building as long as the access plans are generated at run-time

SQL tĩnh luôn được compile lúc compile time. Chính vì thế, sử dụng SQL tĩnh giúp hạn chế được số lần compile SQL khi sử dụng.

// Một câu SQL như thế này có thể xem là ví dụ hay cho Static
// Sẽ được compile lúc compile time
// Không có điều kiện nào làm SQL thay đổi
SELECT
id, first_name, last_name
FROM
customers;

1.2 Điểm yếu

Tuy nhiên, compile một lần cũng có điểm không tốt. Trường hợp SQL search với các điều kiện search là flexible, sẽ phải viết rất nhiều câu SQL cho từng điều kiện search khác nhau. Rõ ràng là cực kì vất vả (đôi khi là không thể thực hiện được)

Ví dụ, như trường hợp sử dụng BackLog (vô cùng quen thuộc với tầng lớp SE). Backlog có một phần tính năng search tên là Advanced Search.

static-sql-la-gi-dynamic-sql-la-giBacklog cho phép custom các field muốn tìm kiếm. Các điều kiện search có thể matching với nhau.
Nguồn/Source: backlog.com

Có thể thấy là advanced search có tới 10 field có thể custom, không thể biết được người sử dụng sẽ chọn tìm kiếm theo bao nhiêu field.

Lúc này Static SQL chỉ như Minh giáo, phát triển nhanh, nhiều giáo đồ sẵn sàng chết để hộ giáo, nhưng lại không thể thay đổi linh hoạt như các môn phái khác trong võ lâm.

Thật sự rất khó để viết SQL tĩnh, lúc này SQL động (lục đại môn phái) sẽ ra tay.

  "Làm PM, theo anh không cần biết về code, nhưng phải hiểu về SQL, database, những khái niệm cơ bản của code"
  Các thao tác cơ bản với Database SQL Server (tạo mới database, table,...)

2. SQL động – Dynamic SQL – Lục đại môn phái

Dynamic SQL is SQL statements that are constructed at runtime; for example, the application may allow users to enter their own queries.

SQL động là những câu SQL có thể được xây dựng trong khi chương trình thực thi. Ví dụ thường thấy ở đây là ứng dụng có thể chấp nhận cho người dùng nhập những câu Query mà họ muốn

2.1 Điểm mạnh

Dynamic SQL thật sự mang tới cái động như tên của nó. Trường hợp ứng dụng cần thay đổi logic, hay các điều kiện tìm kiếm được người dùng thay đổi liên tục. Dynamic SQL tỏ ra vượt trội hơn hẳn so với Static.

Ví dụ dưới đây cho thấy sự linh hoạt của SQL động khi có nhiều điều kiện search muốn match với nhau. Trên thực tế, có thể còn phức tạp hơn, bao gồm các điều kiện search IN, OR, AND, LIKE, …

// Một câu SQL như thế này có thể xem là ví dụ hay cho Dynamic
// Sẽ được compile lúc run time
// Thực tế có thể có nhiều condition phức tạp hơn - ví dụ bằng Mybatis
SELECT
id
, first_name
, last_name 
FROM
customers cus 
WHERE 
cus.id = 1
// Nếu có nhập tuổi - search theo tuổi
<if test="userCondition.age != null">
AND cus.age = request.age
</if>
// Nếu có nhập địa chỉ - And thêm điều kiện này
<if test="userCondition.address != null">
AND cus.address = request.address
</if>
<if test="userCondition.phone != null">
AND cus.phone = request.phone
</if>

Dynamic SQL has only one advantage over static statements which can be clearly noticed once the application is edited or upgraded, so with Dynamic statements there’s no need for pre-compilation or re-building as long as the access plans are generated at run-time,

SQL động rõ ràng có lợi thế rõ rệt so với Static trong trường hợp ứng dụng sửa đổi hoặc cập nhật, Dynamic không cần phải compilation hoặc re-building lại lúc khởi chạy.

2.2 Điểm yếu

Về tốc độ, SQL động rõ ràng không thể so với SQL tĩnh (It is less swift and efficient – không nhanh chóng bằng)

Thực tế làm việc của mình với Dynamic SQL (Dự án gần đây nhất là MyBatis – các bạn có thể đọc bài về Mybatis tại đây), một số câu SQL động vô cùng phức tạp.

Mặc dù linh động để thêm, xóa điều kiện, nhưng trường hợp người viết và người maintainance khác nhau. Thực sự thì we are in a big fu**ing trouble. Khá là khỏ để hiểu, rồi đi fix bug hay gì gì đó.

3. Sự khác biệt

Static SQL Dynamic SQL
Không thể thay đổi lúc runtime, gần giống như hard coded.
Không thể uyển chuyển như Dynamic SQL
Một số condition trong SQL tĩnh không thể thực hiện được như Static SQL
Performance tốt do đã được biên dịch trước lúc runtime Performance kém hơn so với Static SQL do tạo các câu SQL trong lúc runtime
Trường hợp có nhiều điều kiện thay đổi có thể tạo ra nhiều câu SQL khác nhau. Sẽ phải viết rất nhiêu câu Trường hợp có nhiều điều kiện tạo ra các câu SQL khác nhau, SQL viết kiểu này sẽ không được clear cho lắm

4. Tham khảo

Lúc cần tham khảo hoặc research, cần lưu ý các tên gọi khác của Static và Dynamic SQL. Static còn được gọi là EMBEDDED SQL. Dynamic còn có tên gọi khác là INTERACTIVE SQL.

Để tìm kiếm thêm thông tin, các vị bằng hữu trong giang hồ có thể tham khảo:

Bài viết gốc được đăng tải tại kieblog.vn

Có thể bạn quan tâm:

Xem thêm IT Jobs for Developer hấp dẫn trên TopDev

Giới thiệu JUnit

Giới thiệu JUnit

Bài viết được sự cho phép của smartjob.vn

1. Khái niệm unit test

Unit test là một phương pháp kiểm thử phần mềm mà các đơn vị cá nhân của mã nguồn, bộ một hoặc chương trình máy tính nhiều module cùng với các dữ liệu liên quan đến kiểm soát, thủ tục sử dụng và quy trình vận hành, được kiểm tra để xác định xem họ có phù hợp để sử dụng

  Hướng dẫn viết unit test trong React
  Unit Test là gì? Khái niệm và vai trò

2. Giới thiệu về framework JUnit

  • JUnit là một framework để unit test cho các ngôn ngữ lập trình Java.
  • JUnit quan trọng trong sự phát triển của phát triển thử nghiệm điều khiển

3. Hello word JUnit Test trong Netbean 8.0

Step 1: Tạo 1 project java hoặc java web

Lớp ToanHoc.java là lớp xử lý nghiệp vụ.

Step 2: Tạo lớp test cho lớp ToanHoc : ToanHocTest.java

Step 3: import thư viện JUnit.jar cho Test Library:

Step 4: Viết unit test trong lớp: ToanHocTest.java

Ấn Alt+F6 để chạy chương trình test

Bài viết gốc được đăng tải tại smartjob.vn

Có thể bạn quan tâm:

Xem thêm Việc làm Developer hấp dẫn trên TopDev

Chinh Phục Các Bài Test Tuyển Dụng Với Những Bí Quyết Hay Ho

test tuyển dụng
Chinh Phục Các Bài Test Tuyển Dụng Với Những Bí Quyết Hay Ho

Bộ phận nhân sự là một trong những thành phần quan trọng của bất cứ công ty nào. Công việc của họ được đánh giá hiệu quả khi có thể tuyển được những ứng viên có năng lực và tạo được thành quả cho công ty. Do đó, các đơn vị làm việc trong lĩnh vực nhân sự cũng ngày một chú tâm hơn đến quy trình tuyển dụng cũng như cách thức tuyển dụng để đáp ứng được các tiêu chuẩn khắt khe của mỗi công ty. Và test tuyển dụng được đánh giá là một trong những phần quan trọng giúp nhà tuyển dụng đánh giá một cách nhanh chóng và toàn diện về năng lực của ứng viên.

test tuyển dụng
Làm bài test tuyển dụng hiệu quả hơn khi nắm rõ thông tin về chúng

Test tuyển dụng là gì?

Bên cạnh phỏng vấn và trao đổi các vấn đề chuyên môn để nắm được những thông tin cơ bản cũng như sự tương thích giữa ứng viên với công ty, test tuyển dụng là một bài kiểm tra nhanh có thể đánh giá được năng lực làm việc và đặc điểm tính cách của ứng viên. Thông thường bài test sẽ được thực hiện sau khi ứng viên đã phỏng vấn xong.

Mục đích của bài test tuyển dụng thông thường là để đánh giá một cách tổng quan về năng lực tư duy, tính logic, khả năng xử lý tình huống, sự sắc bén trong việc đề ra và lên kế hoạch, EQ cũng như khả năng ngoại ngữ của ứng viên.

  A/B testing và những tiêu chí chính để đánh giá sự thành công của ASO
  NÊN & KHÔNG NÊN Để Đàm Phán Lương Thành Công Khi Phỏng Vấn

Có những dạng bài test tuyển dụng nào?

Test tuyển dụng thường có hai phần chính là kiểm tra đánh giá năng lực chuyên môn (Aptitude test)đánh giá tính cách (Personality test). Giống như mục tiêu đề ra, các câu hỏi đặt ra trong bài test sẽ có thể giúp công ty và phòng nhân sự đánh giá được một cách chính xác khả năng của ứng viên cũng như các đặc điểm tính cách có đáp ứng được vị trí ứng tuyển và văn hóa công ty hay không.

Kiểm tra đánh giá chuyên môn – Aptitude Test

Đây là bài test phổ biến nhất và được hầu hết các công ty áp dụng trong việc đánh giá ứng viên thông qua bài kiểm tra năng lực. Tùy theo mỗi ngành nghề khác nhau mà các câu hỏi sẽ liên quan đến chuyên ngành mà bạn làm việc. Mục đích chính là để nhà tuyển dụng nhìn thấy được năng lực cũng như sự hiểu biết của ứng viên trong ngành nghề này như thế nào.

aptitude test

Ngoài ra, trong bài test thông thường cũng sẽ kết hợp với một số câu hỏi có thể phản ánh được tư duy logic, xử lý tình huống của ứng viên. Khả năng quan sát và phán đoán của ứng viên cũng sẽ được đánh giá rất cao nếu thể hiện tốt trong bài kiểm tra.

Kiểm tra đánh giá tính cách – Personality Test

personality test

Phần kiểm tra này hiện khá phổ biến với những công ty lớn và chú trọng vấn đề tuyển dụng nhân sự chất lượng, toàn diện. Mục đích của bài kiểm tra tính cách là để đánh giá tính cách và các kỹ năng mềm của ứng viên. Nhìn nhận được sơ bộ về đặc điểm tính cách của ứng viên sẽ giúp họ dễ dàng cho quá trình hòa nhập và phát triển cùng công ty.

Test tuyển dụng trong lĩnh vực IT có gì khác biệt?

Tuyển dụng trong ngành IT sẽ mang tính chuyên môn khá cao và người phỏng vấn cũng sẽ tập trung vào khai thác năng lực và hiểu biết của ứng viên với vị trí mà mình ứng tuyển. Các câu hỏi khi phỏng vấn cũng như trong bài test tuyển dụng thường sẽ xoay quanh các vấn đề như cấu trúc dữ liệu và giải thuật, kiến thức chuyên môn về ngôn ngữ và công nghệ mà bạn sẽ làm việc, cơ sở dữ liệu, luồng dữ liệu,…

Xem thêm 10 Bí quyết tuyển dụng giúp bạn tăng tỉ lệ nhận offer tức thì!

Bạn có thể tham khảo thêm một số nội dung test chuyên môn đã được nhiều người làm qua như:

  • Kiểm tra các kiến thức cơ bản liên quan đến ngôn ngữ, công nghệ.
  • Kiểm tra thuật toán qua các bài test lập trình.
  • Kiểm tra khả năng debug code.
  • Kiểm tra khả năng ngoại ngữ. Thông thường sẽ là tiếng Anh, riêng với các công ty Nhật Bản thì tiếng Nhật sẽ được ưu tiên hơn.
  • Một số nơi có thêm bài kiểm tra GMAT.

Về những câu hỏi liên quan đến đặc điểm tính cách, đây được đánh giá là vấn đề mang tính thách thức khá cao với ứng viên trong lĩnh vực lập trình. Do đặc thù nghề nghiệp, một số lập trình viên thường không có khả năng giao tiếp tốt và linh hoạt trong các tình huống. Chính vì thế, để thành công khi làm các bài test tuyển dụng trong ngành IT, bạn cũng nên tìm hiểu qua về văn hóa làm việc của công ty mình ứng tuyển.

  Viết mẫu tin tuyển dụng (JD) sao cho “ngầu” ?

Hiện nay, có khá nhiều các công ty IT có văn phòng đặt tại Việt Nam nhưng trụ sở chính ở nước ngoài như Nhật Bản, Singapore, Tây Âu,… Do đó, để thông qua phỏng vấn dễ hơn, bạn nên tìm hiểu kỹ càng về văn hóa công ty, đặc điểm tính cách và cách làm việc của nước đó.

Đa phần các công ty có bài test tuyển dụng đều là những công ty chuyên nghiệp và nghiêm túc trong công tác tuyển nhân sự mới. Chính vì thế, để không bỏ lỡ cơ hội, bạn nên có sự tìm tòi và nghiên cứu kỹ lưỡng về chuyên môn cũng như cách làm việc của công ty. Thực hiện thêm các bài test mẫu để làm quen với các dạng đề hiện có. Đó chính là bí quyết cho sự thành công.


Tuyển Dụng Nhân Tài IT Cùng TopDev
Đăng ký nhận ưu đãi & tư vấn về các giải pháp Tuyển dụng IT & Xây dựng Thương hiệu tuyển dụng ngay!
Hotline: 028.6273.3496 – Email: contact@topdev.vn
Dịch vụ: https://topdev.vn/page/products

Có thể bạn quan tâm:

Xem thêm Việc làm Developer hấp dẫn trên TopDev

Một số lệnh console hữu ích khi debug website

Một số lệnh console hữu ích khi debug website

Bài viết được sự cho phép của tác giả Tino Phạm

Thông thường bạn dùng để hiển thị bất kỳ thông tin gì cần xem. Tuy nhiên có những cách hiệu quả hơn giúp bạn dễ dàng hơn trong việc xem thông tin của từng loại dữ liệu cũng như kiểm tra performance của các functions mà bạn viết.

  Console Javascript quá kinh khủng
  Console không chỉ có phương thức log!

console.dir()

Hiển thị dữ liệu theo dạng cây phân cấp, bạn có thể tương tác để xem các thuộc tính bên trong.

console-dir

console.table()

Một hàm rất hay mà bạn có lẽ sẽ sử dụng thường xuyên là console.table(). Như tên gọi của nó, hàm này cho phép hiển thị dữ liệu dưới dạng bảng. Không chỉ dùng cho các dữ liệu dạng bảng, console.table() còn có thể đọc các thuộc tính của đối tượng phức tạp để hiển thị. Tất nhiên không thể thiếu, bạn có thể nhấn vào tiêu đề của một cột để table sắp xếp dữ liệu theo cột đó.

console-table

Ngoài ra, nếu chỉ cần xem một vài cột, bạn có thể thêm tùy chọn tên các cột cần hiển thị:

console-table-filter

Bài viết gốc được đăng tải tại thangphampt.wordpress.com
Có thể bạn quan tâm:
Xem thêm Việc làm Developer hấp dẫn trên TopDev