05 Dec 2007
Setelah pada artikel sebelumnya kita berhasil mengakses database, kali ini kita akan membuat tampilan berbasis web yang menggunakan kode program kita kemarin.
Fiturnya tidak terlalu sulit, dari tabel T_PERSON kemarin kita akan buatkan beberapa tampilan untuk mengelola data Person. Tampilan yang akan kita sediakan adalah:
- daftar semua Person
- informasi Person yang dipilih
- form untuk membuat object Person baru
- form untuk mengedit object Person yang sudah ada
Sebelum kita mulai, ada baiknya kita mengetahui cara kerja Spring dalam mengelola aplikasi web. Sequence diagram berikut akan memudahkan pemahaman kita.

Seperti kita lihat pada gambar, semua request akan diterima oleh DispatcherServlet. Mereka yang pernah membaca buku Core J2EE Pattern akan segera mengenali jurus ini, yang sering disebut dengan istilah FrontController. DispatcherServlet akan menyuruh handler mapping untuk memilih class yang akan menangani request. Ada beberapa implementasi handler mapping, diantaranya:
- BeanNameUrlHandlerMapping
- SimpleUrlHandlerMapping
- ControllerClassNameHandlerMapping
Class yang menangani request disebut dengan istilah Controller. Class Controller ini yang akan kita tulis sendiri. Spring menyediakan beberapa superclass Controller yang bisa kita subclass untuk mengurangi kode yang harus ditulis. Beberapa superclass yang disediakan Spring antara lain:
- Controller
- MultiActionController
- SimpleFormController
- AbstractWizardController
Selain membuat turunan dari superclass di atas, kita juga bisa membuat class biasa yang dilengkapi dengan annotation. Pada artikel ini kita tidak akan membuat turunan apa-apa, karena semua bisa dikerjakan dengan annotation.
Tanggung jawab controller selain memproses request adalah menentukan nama template yang akan digunakan untuk menampilkan hasil pemrosesan controller. Spring menyebut template ini dengan istilah View. Kita cuma perlu menyebutkan nama View dan Spring yang akan mencarikan file template yang sesuai dan kemudian mengisi datanya. Proses mencarikan template ini ditangani oleh ViewResolver. Ada beberapa implementasi ViewResolver, antara lain untuk memproses template berjenis:
- JSP dan JSTL
- Freemarker atau Velocity
- XML dengan XSLT
- Jasper Report
- Document View (PDF dan XLS)
Sekarang setelah kita mengetahui arsitektur umum dari aplikasi web Spring, kita bisa segera coding. Class-class yang akan kita buat adalah:
- PersonController. Class ini akan menangani tampilan daftar Person dan detail Person.
- PersonFormController. Class ini akan menangani tampilan pengeditan object Person, baik yang belum terdaftar maupun yang sudah ada di dalam database.
File konfigurasi yang akan kita buat adalah:
- web.xml. Ini adalah konfigurasi standar untuk semua aplikasi web dengan Java.
- tutorial-servlet.xml. Ini adalah konfigurasi DispatcherServlet untuk menampung deklarasi HandlerMapping, Controller, dan ViewResolver.
Untuk menampilkan halaman web, kita akan menggunakan template engine Velocity. Velocity adalah template engine yang kecil dan ringan, tapi fiturnya cukup lengkap dan mudah digunakan. Saya lebih suka menggunakan Velocity daripada JSP, karena JSP membutuhkan kompilasi menjadi Servlet dan kemudian menjadi bytecode. Ini menyebabkan halaman JSP lebih sulit didebug bila terjadi error. Selain itu, kompilasi JSP membutuhkan dua kompiler, satu untuk JSP ke Servlet, dan satu lagi untuk Servlet menjadi bytecode. Dengan demikian, kita harus menginstal JDK di server. Tanpa JSP, kita dapat menggunakan Servlet container yang ringan dan kecil seperti Jetty atau Winstone dan tidak perlu menginstal JDK, cukup JRE saja.
Template untuk menampilkan daftar orang dibuat dalam HTML yang sudah disisipi kode Velocity, disimpan dengan nama personlist.html
. Kodenya terlihat seperti ini.
personlist.html
<html>
<head>
<title>:: List of All Person ::</title>
</head>
<body>
<table border="0" cellpadding="2" cellspacing="2">
<tr>
<th>Name</th>
<th>Email</th>
<th> </th>
</tr>
#foreach($person in $personList)
<tr>
<td>$person.Name</td>
<td>$person.Email</td>
<td>
<a href="personform?person_id=$person.Id">edit</a> |
<a href="persondetail?person_id=$person.Id">view</a>
</td>
</tr>
#end
</table>
</body>
</html>
Kode yang diawali dengan tanda # merupakan perintah dalam Velocity. Dengan menggunakan perintah #foreach
, kita melakukan looping untuk setiap baris record.
Kode yang diawali tanda $ merupakan variabel dalam Velocity. Isi variabel ini nantinya akan kita sediakan melalui controller Spring.
Untuk menampilkan detail informasi Person, kita buat persondetail.html
. Kodenya seperti ini.
persondetail.html
<html>
<head>
<title>:: $person.Name's Detail Info ::</title>
</head>
<body>
<table>
<tr>
<td>Nama</td>
<td>$person.Name</td>
</tr>
<tr>
<td>Email</td>
<td>$person.Email</td>
</tr>
</table>
</body>
</html>
Sekarang mari kita isi template tersebut dengan data yang dibutuhkannya. Template personlist.html
membutuhkan data List dengan nama variabel personList, sedangkan `persondetail.html` membutuhkan data Person dengan nama variabel person.
Pertama, kita akan mengisi personlist.html
. Template ini akan disuplai oleh PersonController, melalui method yang bernama list. Berikut kode programnya.
PersonController.java
package tutorial.spring25.ui.springmvc;
@Controller
public class PersonController {
private PersonDao personDao;
@Autowired
public void setPersonDao(PersonDao personDao) {
this.personDao = personDao;
}
@RequestMapping("/personlist")
public ModelMap list(){
return new ModelMap(personDao.getAll());
}
}
Mudah bukan? Cukup panggil method personDao.getAll, kemudian masukkan hasilnya ke dalam ModelMap.
Kita melihat beberapa annotation pada kode ini. Annotation @Controller
merupakan penanda bagi Spring bahwa class ini adalah sebuah Controller. Kelas yang memiliki annotation ini akan dipindai pada saat start-up dan diregistrasi ke ApplicationContext
. Annotation @Autowired
menyuruh Spring untuk menginjeksikan object PersonDao
. Dengan annotation @RequestMapping
, kita menentukan bahwa request menuju ke http://namaserver:port/namaaplikasi/namaservlet/personlist
akan ditangani oleh method ini.
Pada saat dideploy, DispatcherServlet
milik Spring akan menemukan dan memanggil method ini. Kemudian, dia akan menerima hasilnya berupa ModelMap untuk kemudian diserahkan ke ViewHandler Velocity untuk digabungkan dengan template dan menghasilkan halaman HTML.
Object yang kita berikan pada ModelMap akan diberi nama oleh Spring secara otomatis. Karena kita mensuplai object dengan tipe List<Person>
, maka Spring akan memberikan nama personList
. Demikian juga pada controller berikutnya kita akan memberikan object bertipe Person
ke controller, Spring akan memberikan nama person pada object tersebut. Dengan nama itulah ($person
) kita mengaksesnya di template Velocity.
Cukup satu dulu implementasi kita. Sekarang tiba saatnya konfigurasi. Aplikasi kita akan dideploy dengan nama context tutorial-spring25
. Di dalamnya, kita akan memasang DispatcherServlet
yang akan mengambil semua request dengan path /tutorial/*. Jadi, method public ModelMap list
akan diakses melalui URL http://localhost:8080/tutorial-spring25/tutorial/personlist
.
Konfigurasi pertama ada di web.xml. Di sini kita akan mengkonfigurasi DispatcherServlet
dan mendaftarkan applicationContext.xml
. Berikut isi web.xml
web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Tutorial Spring</display-name>
<description>Tutorial Spring 2.5</description>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>tutorial</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>tutorial</servlet-name>
<url-pattern>/tutorial/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
ApplicationContext
yang berisi konfigurasi database dan transaksi, yang dibahas pada artikel sebelumnya, didaftarkan melalui context-param. File konfigurasinya, applicationContext.xml
disimpan di classpath, yaitu di folder WEB-INF/classes
. Oleh karena itu, kita tulis pathnya classpath:applicationContext.xml
ApplicationContext
ini harus diaktifkan pada saat aplikasi web dideploy dan dinon-aktifkan pada saat aplikasi web di-undeploy. Untuk itu, kita harus memasang ContextLoaderListener
untuk memonitor aktifitas aplikasi web.
Selanjutnya, kita daftarkan DispatcherServlet
dengan nama tutorial
. Spring akan mencari file bernama tutorial-servlet.xml
sebagai file konfigurasi DispatcherServlet
ini di dalam folder WEB-INF
. DispatcherServlet
tutorial akan dimapping untuk menangani semua request dengan pola /tutorial/*
Sekarang, mari kita lihat isi tutorial-servlet.xml
.
tutorial-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="tutorial.spring25.ui.springmvc" />
<bean id="velocityConfig"
class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
<property name="resourceLoaderPath" value="/WEB-INF/templates/velocity/" />
</bean>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
<property name="cache" value="true" />
<property name="prefix" value="" />
<property name="suffix" value=".html" />
</bean>
</beans>
Pertama, kita konfigurasi Spring agar memindai isi package tutorial.spring25.ui.springmvc
dan mendaftarkan semua class yang beranotasi @Controller
.
Kedua, kita mengkonfigurasi VelocityConfigurer
untuk mencari template di dalam folder WEB-INF/templates/velocity
.
Terakhir, kita melakukan konfigurasi VelocityViewResolver
untuk menerjemahkan nama view menjadi nama file template. Misalnya kita memberikan nama view personlist
, maka VelocityViewResolver
akan memberikan file WEB-INF/templates/velocity/personlist.html
.
Pembaca yang teliti akan segera protes, “Kita kan tidak pernah menyebutkan nama view di dalam Controller. Lalu dari mana nama view itu didapatkan?”
Baiklah, mari lihat lagi method tersebut.
Method list
@RequestMapping("/personlist")
public ModelMap list(){
return new ModelMap(personDao.getAll());
}
Method tersebut dimapping untuk menerima request http://localhost:8080/tutorial-spring25/tutorial/personlist
. Kalau kita tidak melakukan konfigurasi apa-apa, Spring secara default akan menganggap nama request sama dengan nama view. Jadi method di atas akan menghasilkan nama view personlist
.
Setelah semua konfigurasi di atas selesai, kita bisa langsung membuat paket war untuk dideploy.
Berikutnya, kita akan membuat tampilan informasi detail per Person. Untuk ini, kita membutuhkan parameter person\_id
yang ingin ditampilkan. Sehingga bila kita ingin menampilkan Person dengan id 100, URLnya adalah http://localhost/tutorial-spring25/tutorial/persondetail?person\_id=100
Class PersonController
yang sudah ditambahi method detail tampak seperti ini.
PersonController.java
package tutorial.spring25.ui.springmvc;
@Controller
public class PersonController {
private PersonDao personDao;
@Autowired
public void setPersonDao(PersonDao personDao) {
this.personDao = personDao;
}
@RequestMapping("/personlist")
public ModelMap list(){
return new ModelMap(personDao.getAll());
}
@RequestMapping("/persondetail")
public ModelMap detail(@RequestParam("person_id") Long personId){
return new ModelMap(personDao.getById(personId));
}
}
Untuk mengambil parameter person\_id
, kita tinggal membuat method parameter biasa yang dilengkapi annotation @RequestParam
. Konversi tipe data akan dilakukan oleh Spring. Dengan kata lain, kode ini
@RequestParam("person_id") Long personId
sama dengan ini
Long personId = Long.valueOf(httpRequest.getParameter("person_id"));
bedanya, kita tidak perlu mengimport javax.servlet.HttpServletRequest
.
Setelah selesai, redeploy aplikasi dan coba akses http://localhost/tutorial-spring25/tutorial/personlist
. Dari sana, klik link view. Tampilan detail dari object Person yang dipilih akan segera terlihat.
Demikianlah bagian kedua dari seri Spring 2.5. Pada artikel selanjutnya, kita akan melihat cara mengimplementasikan form untuk mengedit object Person yang sudah ada, maupun membuat object Person yang baru.
03 Dec 2007
Akses database dengan Spring 2.5
Spring 2.5 baru saja keluar. Rilis kali ini membawa penambahan fitur yang cukup signifikan di sisi konfigurasi. Dalam Spring yang baru ini, kita bisa mengkonfigurasi aplikasi melalui annotation. Suatu hal yang sangat bermanfaat untuk mengurangi jumlah baris kode XML kita.
Sebetulnya tidak ada yang salah dengan XML. Walaupun demikian, ada beberapa hal yang menurut saya kurang tepat kalau dikonfigurasi melalui XML, diantaranya:
- konfigurasi transaction
- deklarasi bean standar
Konfigurasi transaction biasanya tergantung dari kode program yang ingin ber-transaction. Bila kita konfigurasi di XML, maka untuk memikirkan satu logika akses database, kita harus melihat di dua tempat yang berbeda; file java dan file XML. Menurut pendapat saya, fitur declarative transaction walaupun kelihatannya mirip konfigurasi, tapi pada dasarnya adalah logika aplikasi. Tempatnya bukan di konfigurasi XML, tapi di kode Java.
Di Spring, kita harus mendaftarkan object aplikasi kita ke dalam object ApplicationContext agar bisa dikelola oleh Spring. Pada rilis sebelumnya, pendaftaran ini dilakukan dalam file XML. Cara ini memiliki incremental cost yang tinggi. Bila kita punya 100 object yang ingin dikelola, maka kita harus punya 100 deklarasi di konfigurasi XML Spring. Sekarang kita bisa menandai object yang akan dikelola Spring melalui annotation. Jadi walaupun ada 100 object, konfigurasi XML kita tidak bertambah.
Ok, cukup berteori. Saatnya melihat contoh kode.
Domain Model
Pada artikel kali ini, kita akan membuat kode akses database untuk class Person
. Class ini tidak istimewa, cuma POJO biasa dengan tiga property: id, name, dan email. Berikut kode program Person.java
.
package tutorial.spring25.model;
public class Person {
private Long id;
private String name;
private String email;
}
Jangan lupa membuat getter dan setter.
Skema Database
Class ini akan kita simpan di database dalam tabel bernama T_PERSON
. Berikut definisinya untuk database MySQL.
create table T_PERSON (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
email VARCHAR(255)
);
Interface Akses Database
Operasi database yang akan kita buat dijelaskan oleh interface PersonDao
, sebagai berikut.
package tutorial.spring25.dao
public interface PersonDao {
public List<person> getAll();
public Person getById(Long id);
public void save(Person p);
}
Untuk tahap pertama, kita akan lihat cara mengakses database dengan JDBC helper yang disediakan Spring. Akses database dengan Hibernate akan dijelaskan pada artikel terpisah.
Implementasi Akses Database
Berikut adalah kerangka implementasi PersonDao
dengan JDBC helper dari Spring. Kita simpan di file bernama PersonDaoSpringJdbc.java
package tutorial.spring25.dao.springjdbc;
@Repository("personDao")
@Transactional(readOnly=true)
public class PersonDaoSpringJdbc implements PersonDao {
@Autowired
public void setDataSource(final DataSource dataSource) {
}
@Override
public List<person> getAll() {
return null;
}
@Override
public Person getById(final Long id) {
return null;
}
@Override
@Transactional(readOnly=false)
public void save(final Person person) {
}
}
Ada beberapa hal yang baru pada kode di atas. Kita melihat ada annotation @Repository
, @Transactional
, dan @Autowired
.
Annotation @Repository
memberi tahu pada Spring bahwa class ini adalah salah satu @Component
dalam aplikasi kita. Semua @Component akan dipindai pada waktu inisialisasi dan kemudian diregistrasi ke dalam object ApplicationContext
milik Spring. Selain @Repository
, @Component
juga memiliki turunan @Service
dan @Controller
. @Service
biasanya digunakan untuk menandai class-class facade atau business delegate. Sedangkan @Controller
digunakan untuk aplikasi web. @Service
dan @Controller
akan kita bahas di artikel terpisah.
Annotation @Transactional
menandakan bahwa semua method dalam class ini akan dijalankan dalam transaksi database. Kita memberikan nilai readOnly=true
pada deklarasi class, menandakan bahwa secara default transaksi hanya digunakan untuk mengambil data dari database. Perhatikan method save. Pada method ini, kita akan memasukkan atau mengubah data dalam database. Untuk satu method ini, kita membutuhkan transaksi yang tidak readOnly. Karena itu, kita override konfigurasi default dengan cara memberikan annotation @Transactional(readOnly=false)
.
Konfigurasi Spring Framework
Sekarang mari kita lihat konfigurasi Application Context. File ini disave dengan nama applicationContext.xml
.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
<context:annotation-config/>
<context:component-scan base-package="tutorial.spring25"/> <!-- tidak perlu deklarasi masing2 DAO -->
<tx:annotation-driven /> <!-- tidak perlu deklarasi transaction setting per method -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driver}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}" />
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource" />
</beans>
Seperti kita lihat di atas, kita tidak lagi membutuhkan deklarasi untuk object personDao
seperti pada Spring sebelumnya. Kita juga tidak perlu membuat konfigurasi transaksi untuk masing-masing method dalam PersonDao
. Sebagai gambaran, kita menghilangkan beberapa baris yang seperti ini.
<bean id="personDaoImpl" class="tutorial.spring25.dao.springjdbc.PersonDaoSpringJdbc">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="personDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="target" ref="personDao"></property>
<property name="transactionManager" ref="transactionManager"></property>
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="save*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
Semakin banyak class DAO kita, deklarasinya juga akan semakin banyak. Pada aplikasi skala menengah, jumlah DAO bisa mencapai ratusan. Bisa dibayangkan dampaknya terhadap file xml tersebut. Dengan mencantumkan satu baris seperti ini,
<context:component-scan base-package="tutorial.spring25"/>
Spring dapat secara otomatis memeriksa seluruh package tutorial.spring dan mendaftarkan semua class yang memiliki annotation @Component
, @Repository
, @Service
, dan @Controller
.
Kita sudah lihat bagaimana keseluruhan kode program ditulis. Kecuali implementasi sebenarnya tentu saja. Sebelum melihat secara detail bagaimana kode program untuk INSERT
dan SELECT
, terlebih dulu kita lihat bagaimana class PersonDaoSpringJdbc
ini digunakan.
Automated Testing
Daripada menggunakan cara yang kurang berwawasan (menggunakan method main), saya akan mengambil pendekatan yang lebih berpendidikan, yaitu menggunakan Unit Test. Lihat artikel saya tentang Unit Test dan Integration Test untuk memahami kode berikut.
Ini adalah kerangka class test untuk PersonDaoSpringJdbc
. Class ini dibuat dengan menggunakan JUnit 4. Isinya masih belum lengkap. Kita akan lengkapi sambil jalan.
package test.spring25.dao.springjdbc
public class PersonDaoSpringJdbcTest {
private static final ApplicationContext applicationContext;
private static final DataSource dataSource;
private static final PersonDao personDao;
@BeforeClass public static void init(){}
@Before public void resetDatabase(){}
@Test public void testGetById(){}
@Test public void testGetAll(){}
@Test public void testSave(){}
}
Seperti kita lihat, kita sudah menggunakan annotation untuk menandai method test dan inisialisasi. Penjelasan tentang JUnit 4 akan dibahas pada artikel terpisah.
Sekarang kita lihat isi masing-masing method. Method init
isinya seperti ini.
@BeforeClass public static void init(){
ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
dataSource = (DataSource) ctx.getBean("dataSource");
personDao = (PersonDao) ctx.getBean("personDao");
}
Method resetDatabase
dijalankan sebelum masing-masing test method. Fungsinya untuk menghapus isi tabel T_PERSON
dan mengisi sampel data sesuai yang kita inginkan. Ini dilakukan menggunakan DBUnit. Isinya sebagai berikut
@Before public void resetDatabase() throws Exception {
final Connection conn = ds.getConnection();
DatabaseOperation.CLEAN_INSERT.execute(new DatabaseConnection(conn), new FlatXmlDataSet(new FileInputStream("fixtures/person.xml")));
conn.close();
}
Sample Data
Method di atas akan menggunakan sampel data yang ada di file person.xml
. Isinya seperti ini,
<dataset>
<T_PERSON
id="100"
name="Endy Muhardin"
email="endy.muhardin@gmail.com"
/>
</dataset>
Cukup satu record saja.
Implementasi Query Database
Pertama kali, kita akan implementasi method getById
. Isi testnya tidak rumit. Cukup jalankan method getById
dan periksa hasilnya.
@Test public void testGetById() throws Exception {
Person endy = personDao.getById(100L);
assertEquals("Endy Muhardin", endy.getName());
assertEquals("endy.muhardin@gmail.com", endy.getEmail());
}
Implementasi getById
dalam PersonDaoSpringJdbc
seperti ini.
public Person getById(Long id) {
return simpleJdbcTemplate.queryForObject("select * from T_PERSON where id=?", new PersonMapper(), id);
}
Cukup satu baris saja.
Mapping dari ResultSet
menjadi Person
Method ini membutuhkan class PersonMapper
untuk mengkonversi object ResultSet
menjadi object Person
. Class ini dibuat menjadi static final inner class dalam PersonDaoSpringJdbc
.
public class PersonDaoSpringJdbc implements PersonDao {
private static final class PersonMapper implements ParameterizedRowMapper<Person>{
@Override
public Person mapRow(final ResultSet rs, final int rowNum) throws SQLException {
final Person result = new Person();
result.setId(rs.getLong("id"));
result.setName(rs.getString("name"));
result.setEmail(rs.getString("email"));
return result;
}
}
}
Implementasi Query lainnya
Selanjutnya, kita akan implementasikan method getAll
. Berikut test methodnya.
@Test public void testGetAll() throws Exception {
List<Person> result = personDao.getAll();
assertEquals(1, result.size());
Person endy = result.get(0);
assertEquals("Endy Muhardin", endy.getName());
assertEquals("endy.muhardin@gmail.com", endy.getEmail());
}
Dan ini implementasi dari method getAll
.
public List<Person> getAll() {
return simpleJdbcTemplate.query("select * from T_PERSON", new PersonMapper(), new HashMap<String, String>());
}
Implementasi Insert Data
Terakhir, mari kita implementasi method save. Method testnya sedikit lebih panjang, karena untuk yakin akan hasilnya, kita harus melakukan query ke database dengan JDBC murni.
@Test public void testSave() throws Exception {
Person dhiku = new Person();
dhiku.setName("Hadikusuma Wahab");
dhiku.setEmail("dhiku@gmail.com");
assertNull(dhiku.getId());
personDao.save(dhiku);
assertNotNull(dhiku.getId());
final Connection conn = ds.getConnection();
final PreparedStatement ps = conn.prepareStatement("select * from T_PERSON where id=?");
ps.setLong(1, dhiku.getId());
final ResultSet rs = ps.executeQuery();
assertTrue(rs.next());
assertEquals(dhiku.getName(), rs.getString("name"));
assertEquals(dhiku.getEmail(), rs.getString("email"));
ps.close();
rs.close();
conn.close();
}
Untungnya implementasi method save
juga hanya satu baris.
@Transactional(readOnly=false)
public void save(final Person person) {
person.setId(simpleJdbcInsert.executeAndReturnKey(new BeanPropertySqlParameterSource(person)).longValue());
}
Ada beberapa hal yang perlu dijelaskan dari kode di atas.
Pertama, kita perlu mengaktifkan transaksi database untuk mengubah isi database. Kita lakukan dengan @Transactional(readOnly=false)
.
Kedua, kita bisa menggunakan fitur terbaru Spring JDBC, yaitu SimpleJdbcInsert
. Fitur ini mampu melihat ke dalam database dan mengambil daftar nama fieldnya. Object ini diinisialisasi pada saat kita menginjeksi DataSource
. Berikut kodenya.
@Autowired
public void setDataSource(final DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
this.simpleJdbcInsert = new SimpleJdbcInsert(dataSource).withTableName("T_PERSON").usingGeneratedKeyColumns("id");
}
Pada saat menginisialisasi object SimpleJdbcInsert
kita memberi tahu Spring tentang nama tabel dan field yang isinya autogenerated, misalnya id
.
Selama nama field dalam T_PERSON
sama dengan nama properti di class Person
, kita bisa menghilangkan kode untuk mapping properti ke PreparedStatement
. Kode yang biasanya tiga baris seperti ini
PreparedStatement ps = conn.prepareStatement("insert into T_PERSON (name, email) values(?,?)");
ps.setString(1, person.getName());
ps.setString(2, person.getEmail());
Dapat direduksi menjadi satu baris seperti ini:
SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(person);
Object parameterSource
ini bisa langsung diumpankan ke simpleJdbcInsert
seperti ini untuk melakukan insert sekaligus mengambil nilai id yang digenerate database.
Long newId = simpleJdbcInsert.executeAndReturnKey(parameterSource).longValue()
Fitur SimpleJdbcInsert
ini sangat bermanfaat kalau entity class kita terdiri dari puluhan field. Adanya method executeAndReturnKey
untuk mengambil auto-generated primary key dari database juga akan sangat membantu kita untuk menghilangkan perbedaan antar database. Biasanya masing-masing merek database memiliki cara yang berbeda-beda untuk mengambil nilai ini.
Sebagai contoh, bila kita lakukan secara manual untuk database MySQL, kodenya akan tampak seperti ini.
@Transactional(readOnly=false)
public void save(final Person person) {
simpleJdbcInsert.execute(new BeanPropertySqlParameterSource(person));
Long newId = simpleJdbcInsert.getJdbcOperations().queryForLong("select last_insert_id()");
person.setId(newId);
}
Demikianlah sekilas tentang penggunaan fitur Spring terbaru untuk mengakses database. Pada artikel selanjutnya, kita akan lihat fitur-fitur baru di sisi MVC framework.
07 Nov 2007
Redmine adalah aplikasi manajemen proyek yang dibuat menggunakan framework Ruby on Rails.
Pada saat artikel ini ditulis, Redmine sudah mencapai versi 0.5.1 yang dirilis 15 Juli 2007.
Selain Redmine, banyak juga aplikasi manajemen proyek lainnya, misalnya:
Redmine mendukung multiple project. Jadi kita bisa menginstal Redmine di perusahaan untuk mengelola semua proyek yang sedang berjalan.
Untuk pengelolaan proyek, Redmine memiliki Gantt chart dan Calendar. Untuk mengelola dokumentasi proyek, kita bisa menggunakan wiki yang sudah tersedia. Tugas dibagikan pada team member dengan menggunakan konsep issue yang akan dijelaskan di bawah. Kita bahkan bisa melihat kode program yang sudah dibuat menggunakan version control browser. Saat ini Redmine dapat melihat isi repository Subversion, CVS, Mercurial, dan Darcs.
Pertama, kita instal dulu Redmine.
Selanjutnya, mari kita langsung saja menggunakan Redmine.
Setelah Redmine terinstal, kita dapat langsung membuka browser dan melihat halaman depan Redmine. Segera klik tombol Login untuk masuk ke dalam sistem.

Default username dan passwordnya adalah admin/admin. Segera masuk ke halaman My Account untuk mengganti password administrator.
Membuat user
Buat user baru melalui menu Administration > Users > New.

Isi informasi yang sesuai di halaman form user baru.

Jangan lupa untuk membuat beberapa user, agar bisa digunakan di
Membuat project
Selanjutnya, kita mendefinisikan project. Buat project baru melalui menu Administration > Projects > New.
Isi informasi tentang project.

Isikan juga informasi tentang version control bila ada.
Berikutnya, tentukan anggota project. Tentu saja saja anggota ini sudah harus didaftarkan dulu seperti pada langkah sebelumnya.

Setelah itu, kita tentukan Version dalam proyek. Version ini bisa iterasi, atau fase, tergantung dari siklus pengembangan yang kita gunakan dalam proyek. Version ini nantinya digunakan sebagai target penyelesaian suatu issue.

Di halaman selanjutnya, kita akan menentukan kategori untuk issue. Semua tugas di dalam Redmine disebut Issue. Ada tiga jenis issue:
-
Feature: Ini digunakan untuk membuat semacam To Do List untuk fitur yang akan dibuat dalam proyek kita.
-
Bug: Ini digunakan untuk mencatat dan melacak status penyelesaian defect dalam proyek kita.
Selain untuk bug aplikasi, saya biasanya menggunakan jenis issue ini untuk mencatat
-
Resiko Proyek
-
Kesalahan dokumen (salah requirement, revisi project schedule, dsb)
-
Masalah yang terjadi dalam proyek
-
Support: fitur ini tidak saya gunakan. Mungkin ini ditujukan untuk pertanyaan dari user yang belum tentu bug.
Saya menggunakan kategori berikut untuk issue:
-
Project Document: Dokumentasi project seperti schedule, progress report, dsb
-
Functional Specification: spesifikasi aplikasi yang ingin dibuat, sering disebut juga dengan dokumen analisa
-
Technical Specification: spesifikasi tentang bagaimana cara membuatnya, sering disebut juga dengan dokumen desain
-
User Documentation: segala issue yang berkaitan dengan dokumen user manual
-
Business Layer: komponen logika bisnis dari aplikasi
-
User Interface Layer: komponen tampilan aplikasi
-
Data Access Layer: komponen aplikasi yang berinteraksi dengan database
Persiapan project selesai. Sekarang kita bisa langsung membuat daftar pekerjaan. Melalui menu, klik Nama Project > New Issue > Feature.

Kita bisa daftarkan tugas yang harus dilakukan dengan mengisi informasi pada formnya. Setelah diisi, tekan Save dan lihat hasilnya pada tampilan daftar issue.

Issue yang sudah didaftarkan dapat dilacak pengerjaannya. Dari tampilan daftar issue, klik nama issue sehingga muncul tampilan detailnya.

Kita bisa klik Log Time untuk memasukkan waktu yang sudah kita gunakan untuk menyelesaikan issue tersebut.

Isikan jumlah jam yang digunakan, misalnya 2 jam, lalu klik Save. Selanjutnya kita akan diarahkan ke halaman Spent Time. Halaman ini menunjukkan jumlah waktu yang sudah digunakan untuk berbagai task dalam project. Kita bisa melihat jumlah waktu untuk satu task saja ataupun keseluruhan project.
Sekarang kita sudah memiliki beberapa issue yang terdaftar. Data tersebut ditampilkan oleh Redmine dalam berbagai bentuk, misalnya:
Calendar

Gantt Chart

Report

Activity

Roadmap

Untuk tampilan Activity dan Roadmap mirip sekali dengan Trac. Activity di Trac disebut dengan Timeline. Entah disengaja atau tidak, dari tampilan sampai cara kerjanya tidak dapat dibedakan. Silahkan lihat sendiri.
Timeline Trac

Roadmap Trac

Kita juga bisa melihat isi repository Subversion kita.

Seperti repo browser lainnya, kita bisa melihat perbandingan antara dua versi file yang berbeda.

Perbedaan ini bisa ditampilkan secara inline seperti gambar di atas, atau secara berdampingan seperti ini.

Demikianlah sekilas tentang aplikasi Redmine. Masih banyak fitur Redmine yang belum dieksplorasi, misalnya wiki, document management, dan file management. Mengingat umurnya yang masih muda, besar harapan Redmine akan semakin canggih di masa yang akan datang.
Dengan menggunakan aplikasi ini, kita dapat mengelola berbagai aspek dalam manajemen proyek kita secara terpusat. Redmine gratis dan mudah diinstal, jadi tunggu apa lagi … segera gunakan.
02 Nov 2007
Posting ini dibuat untuk menanggapi diskusi di milis Netbeans.
Saya menyarankan para programmer, daripada menghabiskan waktunya untuk memperdebatkan editor, lebih baik menginvestasikan waktu dan energi untuk memperdalam konsep.
Ekstrimnya, coding dengan Notepad bila perlu.
Salah satu programmer hebat yang saya kenal, sampai hari ini masih coding menggunakan editornya Midnight Commander (MC). Untuk urusan produktivitas, daftar hasil karyanya lebih panjang daripada anda-anda yang berdebat Eclipse vs Netbeans.
Dari keseluruhan hasil karyanya, perkiraan saya paling tidak 75% dibuat dengan editor MC. Dan aplikasi yang ada di sana adalah aplikasi betulan, bukan tugas kuliah, bukan skripsi.
Editor Midnight Commander, kalau ingin tau, terlihat seperti ini:

Tidak ada autocomplete, tidak bisa mouse over terus keluar JavaDoc, tidak bisa klik kanan - Deploy in Tomcat.
Komentar di milis Netbeans tentang coding pakai editor seperti ini:
waduh pake notepad… be-darah2 itu mah codingnyah
Not really … gak juga kok.
Memang lebih lambat daripada pakai Eclipse/Netbeans, soalnya kalo ada syntax error baru ketahuan setelah compile.
Workflownya mirip dengan coding PHP.
Edit kodenya, save, refresh browser.
Kalau Java, edit, save, Alt-Tab ke konsol, ant compile.
Nanti kan keluar pesan errornya di baris berapa.
Buat yang belum merasakan, memang terkesan seperti latihan kungfu di Shaolin.
Keras dan melelahkan.
Tapi ada hasilnya:
-
Bebas mau pakai IDE apa aja.
Bisa pakai the right tools for the right job. Spring development, pakai Eclipse. Desktop development, pakai Netbeans. Bikin aplikasi desktop yang mengakses Webservice, pakai 2-2nya
-
Lebih jeli dalam mendebug.
Sampai saat ini, sebagian besar XML code harus didebug manual, karena memang by nature sulit untuk disupport IDE. Development jaman sekarang, pasti harus melibatkan XML. Dengan coding pakai editor minimalis, kita akan terbiasa melihat error message dan mengartikannya.
-
Lebih mudah mempelajari teknik automation, misalnya Continuous Integration (CI).
Soalnya CI itu mengharuskan kita bisa compile tanpa IDE. Buat yang tidak terbiasa (apalagi lulusan VB/Delphi), pasti akan kaget, bagaimana bisa compile tanpa IDE?
-
Lebih cepat belajar bahasa pemrograman baru.
Kalau kita terbiasa coding pakai IDE, begitu belajar bahasa baru, hal pertama yang kita pikirkan adalah, “IDE-nya pakai apa ya??”. Lalu menghabiskan dua minggu debat di milis, baru pilih salah satu. Download butuh waktu 2 hari. Setelah terinstal, baca Help setengah jam, baru tau cara bikin file baru. Setelah coding Hello World, bingung cari tombol Compile dan Run, 2 jam lagi untuk baca Help. Notepad coder tidak. Langsung buka apapun editor yang tersedia, mulai coding dan compile via command-line. Pada saat IDE-code baru bisa menjalankan Hello World, Notepad-coder sudah paham tentang duck-typing.
Satu pertanyaan yang selalu saya ajukan kalau ada yang tanya tentang IDE, RAD tools, dan sejenisnya.
“Apakah ini coding untuk belajar, atau coding untuk mencari nafkah??”
Kalau jawabannya belajar, entah itu mahasiswa, atau pro yang belajar framework baru, gunakan tools sesedikit mungkin.
Alasannya adalah, karena tujuan kita untuk paham, bukan untuk selesai.
Semakin banyak/canggih tools yang digunakan, pemahaman semakin minim.
Tambahan lagi, kalau ada error (yang mana akan sering terjadi, namanya juga lagi belajar) sulit membedakan error karena salah pakai tools, atau error di kode program kita.
Belajar Spring AOP dengan Notepad, tidak mungkin notepad yang salah, pasti kode kita.
Belajar Spring AOP dengan Netbeans/Eclipse, ada kemungkinan tools tersebut salah loading classpath, punya asumsi sendiri tentang struktur folder, dsb.
Kalau jawabannya untuk mencari nafkah, gunakan tools tercanggih yang bisa diperoleh dengan halal.
Hal ini hanya mungkin jika kita tidak terikat pada tools tertentu.
Tidak akan bisa coding desktop dengan Netbeans, kemudian buka Eclipse untuk coding server-side backend.
Demikian saran dari saya.
26 Oct 2007
Maraknya akses internet 3G rupanya berimbas pada turunnya harga layanan GPRS. Salah satu yang sudah turun harga adalah Mentari, dari Rp. 5/kB menjadi Rp. 1/kB, setidaknya sampai Januari 2008.
Dengan adanya perkembangan yang menggembirakan ini, saya segera mengaktifkan koneksi GPRS tersebut agar bisa digunakan dari laptop. Caranya tidak terlalu sulit, seperti akan kita lihat segera.
Hardware
Laptop saya NEC Versa E3100, tepatnya seri E3100-1800DR. Handphone yang saya gunakan adalah LG KG300, seperti yang diiklankan Agnes Monica di TV. Jadi kalau kapan-kapan Agnes mau setting GPRS juga, bisa lihat artikel ini :D
Koneksi handphone ke laptop menggunakan kabel data, karena laptop saya tidak ada blututnya.
Software
Saya menggunakan Ubuntu Gutsy Gibbon 7.10, yang sudah dilengkapi dengan wvdialconf dan wvdial untuk melakukan koneksi. Tidak perlu ada instalasi software tambahan, karena kedua aplikasi ini sudah terinstal dari sananya.
Provider
Saya pakai Mentari. Sebelum mencoba koneksi melalui komputer, pastikan dulu kita bisa browsing langsung dari HP. Ini untuk memastikan bahwa dari HP ke Provider sudah OK. Jangan sampai sudah pusing-pusing utak-atik Ubuntu, ternyata masalahnya ada di HP.
Untuk LG KG300, kita bisa konfigurasi otomatis melalui OTA. Cukup kirim SMS ke 3000 dengan isi sebagai berikut:
Nanti kita akan terima SMS, yang bila dibuka akan ada konfirmasi apakah kita ingin mengkonfigurasi GPRS. Tentu saja jawab Yes.
Silahkan browsing ke Yahoo di http://m.yahoo.com atau Gmail di http://gmail.com/app.
Bila Anda punya akun YM, bisa dicoba chatting di sana.
LG KG300 punya fasilitas Java dengan versi MIDP 2.0. Bila malas chat melalui antarmuka Yahoo, bisa gunakan aplikasi shMessenger.
Konfigurasi
Setelah lancar browsing dan chatting di handphone, kini tiba saatnya kita browsing di laptop. Hubungkan handphone ke laptop melalui kabel data yang disediakan. Nanti di handphone akan muncul pertanyaan, apakah kita ingin terhubung sebagai Storage Media atau COM2. Pilih COM2. Storage Media dipilih bila ingin melakukan transfer file.
Oh iya, untuk memudahkan troubleshooting, selama melakukan konfigurasi, tampilkan kernel log dengan perintah tail -f /var/log/messages
di konsol. Biarkan window ini tetap terbuka selama konfigurasi.
Di kernel log akan muncul laporan deteksi modem seperti ini:
Oct 26 09:19:28 sweetdreams kernel: [ 507.608000] usb 1-2: new full speed USB device using uhci_hcd and address 2
Oct 26 09:19:29 sweetdreams kernel: [ 507.792000] usb 1-2: configuration #1 chosen from 1 choice
Oct 26 09:19:29 sweetdreams kernel: [ 508.100000] cdc_acm 1-2:1.1: ttyACM0: USB ACM device
Oct 26 09:19:29 sweetdreams kernel: [ 508.104000] usbcore: registered new interface driver cdc_acm
Oct 26 09:19:29 sweetdreams kernel: [ 508.104000] /build/buildd/linux-source-2.6.22-2.6.22/drivers/usb/class/cdc-acm.c: v0.25:USB Abstract Control Model driver for USB modems and ISDN adapters
Setelah itu, jalankan wvdialconf sebagai root.
sudo wvdialconf
Editing `/etc/wvdial.conf'.
Scanning your serial ports for a modem.
Modem Port Scan: S0 S1 S2 S3 SL0
WvModem: Cannot get information for serial port.
ttyACM0: ATQ0 V1 E1 -- OK
ttyACM0: ATQ0 V1 E1 Z -- OK
ttyACM0: ATQ0 V1 E1 S0=0 -- OK
ttyACM0: ATQ0 V1 E1 S0=0 &C1 -- OK
ttyACM0: ATQ0 V1 E1 S0=0 &C1 &D2 -- OK
ttyACM0: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0 -- OK
ttyACM0: Modem Identifier: ATI -- MTK2
ttyACM0: Speed 4800: AT -- OK
ttyACM0: Speed 9600: AT -- OK
ttyACM0: Speed 19200: AT -- OK
ttyACM0: Speed 38400: AT -- OK
ttyACM0: Speed 57600: AT -- OK
ttyACM0: Speed 115200: AT -- OK
ttyACM0: Speed 230400: AT -- OK
ttyACM0: Speed 460800: AT -- OK
ttyACM0: Max speed is 460800; that should be safe.
ttyACM0: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0 -- OK
Found an USB modem on /dev/ttyACM0.
/etc/wvdial.conf: Can't open '/etc/wvdial.conf' for reading: No such file or directory
/etc/wvdial.conf: ...starting with blank configuration.
Modem configuration written to /etc/wvdial.conf.
ttyACM0: Speed 460800; init "ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0"
Hasilnya adalah sebuah file /etc/wvdial.conf
sebagai berikut:
[Dialer Defaults]
Init2 = ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
Modem Type = USB Modem
; Phone =
ISDN = 0
; Username =
Init1 = ATZ
; Password =
Modem = /dev/ttyACM0
Baud = 460800
Edit menjadi seperti ini:
[Dialer Defaults]
Modem = /dev/ttyACM0
Init1 = ATZ
Init2 = ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
Init3 = AT+CGDCONT=1,"IP","indosatgprs"
Stupid Mode = on
Modem Type = USB Modem
Phone = *99***1#
ISDN = 0
Username = indosat
Password = indosat
Baud = 460800
Go Online
Kalau sudah, saatnya kita online. Gunakan perintah wvdial untuk melakukan dial-up.
sudo wvdial
WvDial<*1>: WvDial: Internet dialer version 1.56
WvModem<*1>: Cannot get information for serial port.
WvDial<*1>: Initializing modem.
WvDial<*1>: Sending: ATZ
WvDial Modem<*1>: ATZ
WvDial Modem<*1>: OK
WvDial<*1>: Sending: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
WvDial Modem<*1>: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
WvDial Modem<*1>: OK
WvDial<*1>: Sending: AT+CGDCONT=1,"IP","indosatgprs"
WvDial Modem<*1>: AT+CGDCONT=1,"IP","indosatgprs"
WvDial Modem<*1>: OK
WvDial<*1>: Modem initialized.
WvDial<*1>: Sending: ATDT*99***1#
WvDial<*1>: Waiting for carrier.
WvDial Modem<*1>: ATDT*99***1#
WvDial Modem<*1>: CONNECT
WvDial Modem<*1>: ~[7f]}#@!}!} } }2}"}&} } } } }#}$@#}'}"}(}"R[04]~
WvDial<*1>: Carrier detected. Starting PPP immediately.
WvDial<Notice>: Starting pppd at Fri Oct 26 09:25:58 2007
WvDial<Notice>: Pid of pppd: 6028
WvDial<*1>: Using interface ppp0
WvDial<*1>: local IP address 10.33.41.205
WvDial<*1>: remote IP address 10.64.64.64
WvDial<*1>: primary DNS address 202.155.0.10
WvDial<*1>: secondary DNS address 202.155.46.77
Setelah itu, silahkan coba browsing. Jangan tutup window wvdial supaya tidak disconnect.
Untuk menghentikan koneksi, cukup ketik Ctrl-C di window wvdial.
Caught signal 2: Attempting to exit gracefully...
WvDial<*1>: Terminating on signal 15
WvDial<*1>: Connect time 1.2 minutes.
WvDial<*1>: Disconnecting at Fri Oct 26 09:27:20 2007
Kita dapat melihat keseluruhan sesi internet, berikut jumlah trafik yang kita gunakan di kernel log, sebagai berikut.
Oct 26 09:25:58 sweetdreams pppd[6028]: pppd 2.4.4 started by root, uid 0
Oct 26 09:25:58 sweetdreams pppd[6028]: Using interface ppp0
Oct 26 09:25:58 sweetdreams pppd[6028]: Connect: ppp0 <--> /dev/ttyACM0
Oct 26 09:26:00 sweetdreams pppd[6028]: PAP authentication succeeded
Oct 26 09:26:12 sweetdreams pppd[6028]: Could not determine remote IP address: defaulting to 10.64.64.64
Oct 26 09:26:12 sweetdreams pppd[6028]: local IP address 10.33.41.205
Oct 26 09:26:12 sweetdreams pppd[6028]: remote IP address 10.64.64.64
Oct 26 09:26:12 sweetdreams pppd[6028]: primary DNS address 202.155.0.10
Oct 26 09:26:12 sweetdreams pppd[6028]: secondary DNS address 202.155.46.77
Oct 26 09:27:19 sweetdreams pppd[6028]: Terminating on signal 15
Oct 26 09:27:19 sweetdreams pppd[6028]: Connect time 1.2 minutes.
Oct 26 09:27:19 sweetdreams pppd[6028]: Sent 0 bytes, received 0 bytes.
Oct 26 09:27:19 sweetdreams pppd[6028]: Connection terminated.
Oct 26 09:27:19 sweetdreams pppd[6028]: Exit.
Demikianlah cara konfigurasi modem LG KG300 di Ubuntu. Mudah-mudahan dengan adanya artikel ini, Agnes Monica bisa segera online.