2014년 3월 27일 목요일

[스프링3트랜잭션강좌/예제]@Transactional 어노테이션을 이용한 spring framework트랜잭션(Annotation설정방식) [자바교육/자바강좌/자바교육잘하는곳/자바교육추천/자바실무교육/JAVA/JAVA교육/JAVA학원/JAVA실무교육]

[스프링3트랜잭션강좌/예제]@Transactional 어노테이션을 이용한 spring framework트랜잭션(Annotation설정방식) [자바교육/자바강좌/자바교육잘하는곳/자바교육추천/자바실무교육/JAVA/JAVA교육/JAVA학원/JAVA실무교육]
 
:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> 
[예제를 통해 이해하자.]
 
1. Spring Maven Project 생성 (springtr2 라는 이름으로)
 
전체 이클립스 프로젝트 구성은 다음과 같다.
 
 
:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />


<그림, 스프링 선언적트랜잭션예제 이클립스화면>
 
 
 
[pom.xml]
 
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.springframework.samples</groupId>
  <artifactId>springtr1</artifactId>
  <version>0.0.1-SNAPSHOT</version>
 
  <properties>
 
             <!-- Generic properties -->
             <java.version>1.6</java.version>
             <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
             <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 
             <!-- Spring -->
             <spring-framework.version>3.2.3.RELEASE</spring-framework.version>
 
             <!-- Logging -->
             <logback.version>1.0.13</logback.version>
             <slf4j.version>1.7.5</slf4j.version>
 
             <!-- Test -->
             <junit.version>4.11</junit.version>
 
       </properties>
      
       <dependencies>
             <!-- Spring and Transactions -->
             <dependency>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-context</artifactId>
                    <version>${spring-framework.version}</version>
             </dependency>
             <dependency>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-tx</artifactId>
                    <version>${spring-framework.version}</version>
             </dependency>
 
             <!-- Logging with SLF4J & LogBack -->
             <dependency>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-api</artifactId>
                    <version>${slf4j.version}</version>
                    <scope>compile</scope>
             </dependency>
             <dependency>
                    <groupId>ch.qos.logback</groupId>
                    <artifactId>logback-classic</artifactId>
                    <version>${logback.version}</version>
                    <scope>runtime</scope>
             </dependency>
            
             <!-- Test Artifacts -->
             <dependency>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                    <version>${junit.version}</version>
                    <scope>test</scope>
             </dependency>
 
             <dependency>
                    <groupId>aspectj</groupId>
                    <artifactId>aspectjweaver</artifactId>
                    <version>1.5.4</version>
             </dependency>
             <dependency>
                    <groupId>commons-dbcp</groupId>
                    <artifactId>commons-dbcp</artifactId>
                    <version>1.4</version>
             </dependency>
             <dependency>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-jdbc</artifactId>
                    <version>3.2.3.RELEASE</version>
             </dependency>
             <dependency>
                    <groupId>com.oracle</groupId>
                    <artifactId>ojdbc6</artifactId>
                    <version>11.1.0.7.0</version>
             </dependency>
       </dependencies>    
      
       <repositories>
             <repository>
                    <id>oracle</id>
                    <name>ORACLE JDBC Repository</name>
                    <url>http://maven.jahia.org/maven2</url>
             </repository>
       </repositories>
</project>
 
 
 
2. Emp.java
 
package onj.edu.tr1.model;
 
public class Emp {
       int empno;
       String ename;
       int deptno;
      
       public Emp(int empno, String ename, int deptno) {
             this.empno = empno;
             this.ename = ename;
             this.deptno = deptno;
       }
      
       public int getEmpno() {
             return empno;
       }
       public void setEmpno(int empno) {
             this.empno = empno;
       }
       public String getEname() {
             return ename;
       }
       public void setEname(String ename) {
             this.ename = ename;
       }
       public int getDeptno() {
             return deptno;
       }
       public void setDeptno(int deptno) {
             this.deptno = deptno;
       }
      
      
}
 
 
 
3. OnjDao.java
 
package onj.edu.tr1.dao;
 
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
 
import javax.sql.DataSource;
 
import onj.edu.tr1.model.Emp;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
 
//JdbcDaoSupport클래스는 간단한 DataSource 지원하기 위해 JdbcTemplate 클래스를 지원.
//jdbcTemplate  DB연결, PrepatedStatement객체생성, 파라미터주입,  SQL문장실행,
//DB접속종료등을 전담하며 getJdbcTemplate() 통해 얻는다.
@Repository("onjDao")
public class OnjDao{
      
       private JdbcTemplate jdbaTemplate;
 
       @Autowired
       public void setDataSource(DataSource dataSource){
             this.jdbaTemplate = new JdbcTemplate(dataSource);
       }
      
       public List<Emp> getEmpByDeptno(int deptno) {
             String sql = "select empno, ename, deptno from emp where deptno = ?";
             List<Emp> list = jdbaTemplate.query(
                           sql,
                           new Object[] { deptno },
                           new RowMapper() {
                                 public Object mapRow(ResultSet rs, int index) throws SQLException {
                                        int empno = rs.getInt("empno");
                                        String ename = rs.getString("ename");
                                        int deptno =  rs.getInt("deptno");
                                       
                                        return  new Emp(empno, ename, deptno);                                          
                                 }
                           }
             );
             return list;
       }
      
       @Transactional
       public void insertEmps() throws Exception {
             String sql = "insert into emp(empno, ename, deptno) values (?, ?, ?)";
            
             jdbaTemplate.update(sql, new Object[] { 1771, "오자바1" , 10});
            
             jdbaTemplate.update(sql, new Object[] { 1772, "오자바2", 10 });
            
             jdbaTemplate.update(sql, new Object[] { 1773, "오자바3", 10 });
            
             //같은 데이터를 넣으면 키값이 중복되어 DataAccessException 발생(런타임예외)
             jdbaTemplate.update(sql, new Object[] { 1773, "오자바3", 10 });
            
             //아래 주석을 풀면 Exception 발생하므로 트랜잭션은 롤백
             //onjoraclejava.xml에서 rollback-for="Exception"으로 정의했기 때문
             //만약 정의하지 않았다면 주석을 풀더라도 트랜잭션은 롤백안됨
             //throw new Exception();        
            
       }
}
 
 
 
4. OnjService.java
 
package onj.edu.tr1.service;
 
import java.util.List;
 
import onj.edu.tr1.dao.OnjDao;
import onj.edu.tr1.model.Emp;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
@Service("onjService")
public class OnjService {
       @Autowired
       private OnjDao dao;
      
       public OnjService() {}
       public OnjService(OnjDao onjDao) {
             this.dao = onjDao;
       }
      
       public List<Emp> getEmpByDeptno(int deptno) {
             return dao.getEmpByDeptno(deptno);
       }
      
       public void insertEmps() throws Exception{
             dao.insertEmps();
       }
      
}
 
 
5. onjoraclejava.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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
             http://www.springframework.org/schema/aop
             http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
             http://www.springframework.org/schema/context
             http://www.springframework.org/schema/context/spring-context.xsd
             http://www.springframework.org/schema/tx
             http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
 
       <!--  접속할 DB DataSource정의 -->
       <bean name="dataSource"
             class="org.apache.commons.dbcp.BasicDataSource"
             p:driverClassName ="oracle.jdbc.driver.OracleDriver"
             p:url = "jdbc:oracle:thin:@192.168.0.7:1521:onj"
             p:username = "scott"
             p:password = "tiger" />
      
      
       <!--  트랜잭션매니저 정의   -->
       <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
             <property name="dataSource" ref="dataSource"/>
       </bean>
      
       <!-- 트랜잭션 매니저 이름이 transactionManager 경우 transaction-manager 속성 생략가능 -->
       <tx:annotation-driven transaction-manager="transactionManager"/>
      
    <context:component-scan base-package="onj.edu.tr1.dao"/>
    <context:component-scan base-package="onj.edu.tr1.service"/>
    <context:component-scan base-package="onj.edu.tr1.service"/>
      
</beans>
 
 
6. 테스트를 위한 client program
 
package onj.edu.tr1.client;
 
import java.util.List;
 
import onj.edu.tr1.model.Emp;
import onj.edu.tr1.service.OnjService;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
public class OnjEmpTest {
       public static void main(String[] args) throws Exception{
            ApplicationContext ctx = new ClassPathXmlApplicationContext("onjoraclejava.xml");
            
             OnjService onjService = (OnjService)ctx.getBean("onjService");
            
             List<Emp> list = onjService.getEmpByDeptno(10);
             display(list);     
            
             onjService.insertEmps();
            
             list = onjService.getEmpByDeptno(10);
             display(list);
       }
      
       public static void display(List<Emp> list) {
             for(Object e: list) {
                    System.out.print(((Emp)e).getEmpno());
                    System.out.print(((Emp)e).getEname());
                    System.out.println(((Emp)e).getDeptno());                  
              }
       }
}
 
 
 
7. 결과…. 일부러 중복된 데이터를 입력했는데 정상적으로 롤백 되었다.
 
3 02, 2014 5:51:25 오후 org.springframework.context.support.AbstractApplicationContext prepareRefresh
정보: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@bf7916: startup date [Sun Mar 02 17:51:25 KST 2014]; root of context hierarchy
3 02, 2014 5:51:25 오후 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
정보: Loading XML bean definitions from class path resource [onjoraclejava.xml]
3 02, 2014 5:51:25 오후 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
정보: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@15cd7a9: defining beans [dataSource,transactionManager,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor,onjDao,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,onjService,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
7782CLARK10
7839KING10
7934MILLER10
7771Onj110
7772Onj210
7773Onj310
------------------------
3 02, 2014 5:51:26 오후 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
정보: Loading XML bean definitions from class path resource [org/springframework/jdbc/support/sql-error-codes.xml]
3 02, 2014 5:51:26 오후 org.springframework.jdbc.support.SQLErrorCodesFactory <init>
정보: SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase]
Exception in thread "main" org.springframework.dao.DuplicateKeyException: PreparedStatementCallback; SQL [insert into emp(empno, ename, deptno) values (?, ?, ?)]; ORA-00001: 무결성 제약 조건(SCOTT.PK_EMP) 위배됩니다
; nested exception is java.sql.SQLIntegrityConstraintViolationException: ORA-00001: 무결성 제약 조건(SCOTT.PK_EMP) 위배됩니다
 
       at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:245)
       at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
       at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:605)
       at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:818)
       at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:874)
       at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:882)
       at onj.edu.tr1.dao.OnjDao.insertEmps(OnjDao.java:59)
       at onj.edu.tr1.dao.OnjDao$$FastClassByCGLIB$$64f4b819.invoke(<generated>)
       at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
       at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:698)
       at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
       at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96)
       at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260)
       at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
       at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
       at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:631)
       at onj.edu.tr1.dao.OnjDao$$EnhancerByCGLIB$$c6f202f9.insertEmps(<generated>)
       at onj.edu.tr1.service.OnjService.insertEmps(OnjService.java:26)
       at onj.edu.tr1.client.OnjEmpTest.main(OnjEmpTest.java:21)
Caused by: java.sql.SQLIntegrityConstraintViolationException: ORA-00001: 무결성 제약 조건(SCOTT.PK_EMP) 위배됩니다
 
       at oracle.jdbc.driver.SQLStateMapping.newSQLException(SQLStateMapping.java:85)
       at oracle.jdbc.driver.DatabaseError.newSQLException(DatabaseError.java:133)
       at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:206)
       at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:455)
       at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:413)
       at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:1034)
       at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:194)
       at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:953)
       at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1222)
       at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3387)
       at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:3468)
       at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeUpdate(OraclePreparedStatementWrapper.java:1350)
       at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105)
       at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105)
       at org.springframework.jdbc.core.JdbcTemplate$2.doInPreparedStatement(JdbcTemplate.java:824)
       at org.springframework.jdbc.core.JdbcTemplate$2.doInPreparedStatement(JdbcTemplate.java:818)
       at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:589)
       ... 16 more
 
 
8. EMP TABLE SELECT 결과
 
SQL> select empno, ename, deptno from emp where deptno=10;
 
     EMPNO ENAME          DEPTNO
---------- ---------- ----------
      7782 CLARK              10
      7839 KING               10
      7934 MILLER             10
      7771 Onj1               10
      7772 Onj2               10
      7773 Onj3               10
 
9. 이번에는 OnjDao.java에서 중복된 데이터 입력하는 부분을 주석으로 막고 실행해보자.
 
[OnjDao.java]
 
 
package onj.edu.tr1.dao;
 
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
 
import javax.sql.DataSource;
 
import onj.edu.tr1.model.Emp;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
 
//JdbcDaoSupport클래스는 간단한 DataSource 지원하기 위해 JdbcTemplate 클래스를 지원.
//jdbcTemplate  DB연결, PrepatedStatement객체생성, 파라미터주입,  SQL문장실행,
//DB접속종료등을 전담하며 getJdbcTemplate() 통해 얻는다.
@Repository("onjDao")
public class OnjDao{
      
       private JdbcTemplate jdbaTemplate;
 
       @Autowired
       public void setDataSource(DataSource dataSource){
             this.jdbaTemplate = new JdbcTemplate(dataSource);
       }
      
       public List<Emp> getEmpByDeptno(int deptno) {
             String sql = "select empno, ename, deptno from emp where deptno = ?";
             List<Emp> list = jdbaTemplate.query(
                           sql,
                           new Object[] { deptno },
                           new RowMapper() {
                                 public Object mapRow(ResultSet rs, int index) throws SQLException {
                                        int empno = rs.getInt("empno");
                                        String ename = rs.getString("ename");
                                        int deptno =  rs.getInt("deptno");
                                       
                                        return  new Emp(empno, ename, deptno);                                          
                                 }
                           }
             );
             return list;
       }
      
       @Transactional
       public void insertEmps() throws Exception {
             String sql = "insert into emp(empno, ename, deptno) values (?, ?, ?)";
            
             jdbaTemplate.update(sql, new Object[] { 1771, "오자바1" , 10});
            
             jdbaTemplate.update(sql, new Object[] { 1772, "오자바2", 10 });
            
             jdbaTemplate.update(sql, new Object[] { 1773, "오자바3", 10 });
            
             //같은 데이터를 넣으면 키값이 중복되어 DataAccessException 발생(런타임예외)
             //jdbaTemplate.update(sql, new Object[] { 1773, "오자바3", 10 });
            
             //아래 주석을 풀면 Exception 발생하므로 트랜잭션은 롤백
             //onjoraclejava.xml에서 rollback-for="Exception"으로 정의했기 때문
             //만약 정의하지 않았다면 주석을 풀더라도 트랜잭션은 롤백안됨
             //throw new Exception();        
            
       }
}
 
 
 
[cOnjEmpTest.java 실행 결과]
 
3 02, 2014 5:55:36 오후 org.springframework.context.support.AbstractApplicationContext prepareRefresh
정보: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@a6af6e: startup date [Sun Mar 02 17:55:36 KST 2014]; root of context hierarchy
3 02, 2014 5:55:36 오후 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
정보: Loading XML bean definitions from class path resource [onjoraclejava.xml]
3 02, 2014 5:55:37 오후 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
정보: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@52a22d: defining beans [dataSource,transactionManager,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor,onjDao,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,onjService,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
7782CLARK10
7839KING10
7934MILLER10
7771Onj110
7772Onj210
7773Onj310
------------------------
7782CLARK10
7839KING10
7934MILLER10
7771Onj110
7772Onj210
7773Onj310
1771오자바110
1772오자바210
1773오자바310
 
 
[ORACLE EMP 테이블 실행결과]
 
SQL> select empno, ename, deptno from emp where deptno=10;
 
     EMPNO ENAME          DEPTNO
---------- ---------- ----------
      7782 CLARK              10
      7839 KING               10
      7934 MILLER             10
      7771 Onj1               10
      7772 Onj2               10
      7773 Onj3               10
      1771 오자바1            10
      1772 오자바2            10
      1773 오자바3            10
 
 
[OnjDao.java에서 //throw new Exception(); 부분 주석을 출고 테스트]
 
       - onjoraclejava.xml에서 <tx:method name="*" propagation="REQUIRED" rollback-for="Exception"/>인 경우
          
           트랜잭션은 롤백된다.
 
           -  onjoraclejava.xml에서 <tx:method name="*" propagation="REQUIRED“/>인 경우
 
       트앤잭션은 롤백되지 않는다. 정상 commit된다.
 
라클자바커뮤니티에서 운영, 개발자 전문교육, 개인80%환급 오엔제이프로그래밍실무교육센터(www.onjprogramming.co.kr)

평일주간(9:30~18:30) 개강
(3/31)[기업100%환급]PL/SQL,ORACLE HINT,TUNING
(4/07)[기업100%환급]SQL기초에서 Schema Object까지
(4/07)[기업100%환급]자바기초에서 JDBC, Servlet/JSP까지
(4/14)C#4.0,ADO.NET,Network 프로그래밍
(4/14)[기업100%환급]Spring ,MyBatis,Hibernate실무과정

평일야간(19:00~22:00) 개강
(4/01)안드로이드개발자과정
(4/04)웹퍼블리싱 마스터
(4/04)자바기초에서JSP,Ajax,jQuery,Spring3.2,MyBatis까지
(4/04)C#,ASP.NET마스터
(4/07)SQL초보에서실전전문가까지
(4/08)Spring3.X, MyBatis, Hibernate실무과정

주말(10:00~18:00) 개강
(3/29)자바기초에서JSP,Ajax,jQuery,Spring3.2,MyBatis까지
(4/05)웹퍼블리싱 마스터
(4/05)닷넷실무자를위한WPF개발자과정
(4/05)Spring3.X, MyBatis, Hibernate실무과정
(4/05)SQL초보에서실전전문가까지
(4/12)C#,ASP.NET마스터
(4/12)안드로이드개발자과정

댓글 없음:

댓글 쓰기