Dashboard > iBATIS DataMapper > Home > iBATIS 3.0 Whiteboard - Korean
iBATIS 3.0 Whiteboard - Korean
Added by DongGuk Lee, last edited by DongGuk Lee on Jun 17, 2007  (view change)
Labels: 
(None)


이 문서는..

이 문서는 원문의 v.23 버전 기준입니다.
생각을 정리한 문서이기 때문에 내용이 언제든지 바뀔수 있습니다.

Opportunity for Change.

January 11th, 2007 marks the 3rd Anniversary of the iBATIS 2.0. It has served the community well for three years, but times change. The year 2006 was full of innovation and shifts in technology and mindset. The impact of frameworks like Ruby on Rails cannot be ignored. The industry has noticed and finally invested in lightweight frameworks, agile principles and simple solutions first.

Luckily, this shift aligns closer toward iBATIS, not further from it.

iBATIS has always been about simplicity. Since the very beginning, with each release of iBATIS we've made the framework smaller and simpler with fewer dependencies. Yet, we've been able to make the framework faster and more powerful every time.

We hope to continue on this promise with iBATIS 3.0. This whiteboard is where it all begins!

How to work with this page.

  • Committers can change the content of this page.
  • Where there is disagreement among committers, all alternatives should be presented until resolved.
  • Anyone in the community can comment on the content using the comment section below.
  • Committers may make changes to the content based on the comments.
  • Comments acted upon may be deleted to keep the comments relevant to the current state.
  • Comments may be archived if they are deemed irrelevant or are declined (avoid deletion).
  • The committers are charged with ensuring the continuity, consistency and focus of the design is managed.
  • Not all comments or suggestions will be accepted.
  • We'll run a tight ship to ensure this page doesn't turn into a zoo.
  • These rules can change.

iBATIS 3.0의 주제

  • 테스트 주도 개발
  • 성능보다는 코드의 명확성(cleanliness)
  • 유연한 설계 보다는 단순한 설계
  • 하나의 JAR 파일
  • 다른 의존성을 요구하지 않음
  • 더 좋아진 플러그인 지원
  • 다른 곳에서 호스팅되는 이기종 플러그인(소스포지나 CodePlex의 하위 프로젝트) http://sourceforge.net/projects/ibatiscontrib/

The Methodology

  • Clinton says: When I wrote iBATIS 1.0, it was just me. I hacked it out in the most simple and straightforward way possible, with only high level functional tests (actually, they were JPetStore tests). With iBATIS 2.0 I promised myself I'd write unit tests, but I didn't test drive it. The test coverage for 2.0 is around 63%, but looking deeper it's actually not even that good in my own opinion. Therefore, for 3.0 I'd suggest test driving it. The wise man learns from the mistakes of others.

Our team is already very good at developing and maintaining the iBATIS codebase. The source code in our repository is always buildable and even deployable. We don't release when it's stable, because it's always stable. We release when we decide we've hit a milestone that together make a compelling feature set for deployment. We're already quite agile.

To ensure that we have clear direction for the development of iBATIS 3.0, here is a summary of our lifecycle.

Practice Agile Concept
Discussion of new feature/issue on the dev and user mailing lists. customer involvement
Creation of JIRA ticket for new feature/issue. user story
Test drive development of new feature/issue. test always
Commit feature/issue with unit test as a single revision. working software
Automated build and test run upon each checkin. continuous integration
Nightly (or more frequent) builds available for download. release often
Release upon milestone defined by useful feature set decided by the team/community. release planning

기능에 따른 설계

다음 부분은 iBATIS 3.0 기능에 대한 설계를 설명한다.

인터페이스 바인딩

자바 5에서 사용자 경험을 향상시키기 위한 새로운 기회를 제공한다. 그 중에 가장 중요한 것은 애매한 형변환을 제거하는 것과 SQL maps에 대한 문자열 기반의 호출이다. 예를 들어, SQL Map을 호출하는 현재 방법은 다음과 같다.

Employee employee = (Employee)sqlMapper.queryForList("getEmployee", 5);
    //...and...
    List employees = sqlMapper.queryForList("listAllEmployees");

문제점은 명확하게 보인다. 먼저 두가지 경우 매핑 구문의 이름이 문자열 기반이다. 이 사실은 분명히 에러를 발생시킬수 있고 보기에 좋지 않다. IDE에서 제공하는 도움이나 힌트, 또는 컴파일러 경고나 에러를 얻을수 없다.

두번째 문제는 파라미터와 결과의 타입 안정성이다. 예를 들어, 첫번째 경우 파라미터가 정수인것을 누가 말하겠는가.? 그리고 Employee클래스가 반환된다고 누가 말할수 있겠는가.? 다시 말해서 컴파일러 경고나 에러를 볼수는 없다. 오직 런타임 예외만이 발생할수 있을뿐이다.

그리고 마지막으로 collection의 경우에도 문제점은 같다. 실행전에 경고없이 아마 Employee대신에 Dog객체들을 받을지도 모른다.

종합해서 보면 역시 보기에 좋지 않다. JDBC보다는 좋을지 모르지만 역시 좋지 않은것은 분명하다.

자바 5에서 collection을 위해 제네릭을 사용할수 있고 설정을 추가할 필요없이 collection을 위한 일관적인 API를 생성할수 있게 된다. XML에 타입을 명시하고 문자열 이름을 통해 구문을 호출하는 것 대신에, 좀더 서술적으로 효과적으로 사용하고 타입에 안전한 Interface타입을 사용할수 있다. 새롭게 배워야 할 것은 없다. 단지 인터페이스만을 사용하면 된다.

public interface EmployeeMapper {
    Employee getEmployee (int employeeId);
    List<Employee> listAllEmployees();
    }

이렇게 하면 iBATIS는 인터페이스의 구현물을 다루게 된다.

좀더 명확하고 안전한 방법으로 매핑 구문을 호출할수 있다.

Employee emp = empMapper.getEmployee(5);
    //...and...
    List<Employee> employees = empMapper.listAllEmployees();

형변환과 문자열 인자가 없다. 타입에 안전한 파라미터와 결과가 있을뿐이다. 인터페이스를 따르는 추가적인 코드는 XML이나 어노테이션으로 설정을 좀더 줄일수 있을것이다. 대부분, 오버라이드할 필요가 있는 SQL, 결과 또는 파라미터 매핑을 가질것이다.

우리가 알 필요가 있는 모든것을 인터페이스 알려준다.

  • "구문(statement)" 의 이름(메소드 이름과 같은).
  • 파라미터 타입
  • 결과 타입(collection요소 타입을 포함)

그리고 가장 관심을 가지는 것은..

  • 메소드 이름을 사용하는 규칙에 기초하여 SQL을 생성할수 있다. 아래의 다중레벨 설정에서 좀더 많은 정보를 볼수 있다.

바운드(bound) 인터페이스를 사용하는 접근법은 트랜잭션 관리자의 상세한 정보를 보여주지 않고 명확한 매퍼 클래스를 삽입하기 위한 Spring과 같은 프레임워크를 위한 좀더 사용하기 쉽도록 해준다.

트랜잭션, 세션, 그리고 팩토리 – MapperFactory

So where do we get a "Mapper" instance from? We'll still need some sort of central class for iBATIS configuration and control. The name is up for debate, but for now we'll call it MapperFactory.

In general, the MapperFactory is responsible for transactions and mapper instances. The MapperFactory itself will come from some sort of configuration class (again, more about that in the configuration section below).

MapperFactory factory = someConfiguration.buildMapperFactory();
EmployeeMapper employeeMapper = factory.getMapper (EmployeeMapper.class);

I'm not sure that it gets any easier...but that's it. As for transactions...

  • Clinton says: The concept of a session should be merged with that of a transaction. In iBATIS, session and transaction have largely been one and the same. But they were separated for some artificial architectural reason. There are a few things sessions could offer, but for 80% of cases, they don't matter. Similarly batches were separated from transactions, which caused messy nested try/finally blocks. So lets avoid them for now, unless we find a real need for them.

Transactions will be handled either by iBATIS or some 3rd party framework or container. To date they've been handled by a ThreadLocal instance inside a SqlMapClient instance. We get a lot of questions about thread safety due to this approach, so for no technical reason but perhaps a code clarity reason, we could use the following approach:

Transaction tx = factory.startTransaction();
    try {
    // do work
      tx.commit();
    } finally {
    tx.end();
    }

Batches will be handled by simply starting a batched transaction:

Transaction tx = factory.startBatch();
    try {
    // do work
      // balk executes current batch immediately, returns total rows updated
      // (balks on non-batched transactions)
      int totalRowCount = tx.flush();
    // commit executes batch too, returns total rows updated and commits
      totalRowCount = tx.commit();
    } finally {
    tx.end();
    }

Similar to the existing transaction API, we'll still support isolation levels and other transaction options.

Transaction tx = factory.startTransaction(isolationLevel);
    try {
    // do work
      tx.commit();
    } finally {
    tx.end();
    }

If a third party is handling transactions, the managed connection can be explicitly passed:

// Can be handled by encapsulating framework (e.g. Spring)
    Transaction tx = factory.useConnection(conn);
    try {
    //...do work...
      tx.flush(); // executes batched updates immediately
      // commit simply calls commit on the connection
      // if local commits are allowed, otherwise it is ignored.
      tx.commit();
    } finally {
    tx.end(); // Can also be handled by encapsulating framework (e.g. Spring)
    }

Overall this API is cleaner, simpler and is easier to integrate with 3rd party frameworks like Spring. The MapperFactory can be injected into SpringDAOs if access to the transaction manager, batch or isolation levels is needed. To isolate this further, we could separate the transaction manager from the MapperFactory, or even allow the developer to identify their own TransactionManager interface that uses conventions to loosely bind the transaction management methods to their own class (better isolation of the framework).

다중 레벨(Multilevel) 설정

By far the most "old-school" aspect of iBATIS of iBATIS is the configuration. When originally built, XML was thought to be a best practice for configuration. Indeed, XML is still a good choice for iBATIS, as it is a great way to code complex multiline SQL. Therefore XML will remain. However, it will not be the default, or even the solution for most cases.

iBATIS 3.0 will use a multilevel configuration approach. That is, there will be a number of ways to configure iBATIS that work together and override each other. The four ways, in order of application, are:

  • Convention
  • Annotation (overrides convention)
  • XML (overrides convention and annotation)
  • Java API (overrides convention, annotation and XML)

규칙이전에 설정

  • Clinton says: This is the biggest departure from our former principles. Anyone who knows me knows I'm not a big fan of generation. But if we're to adapt to changing best practices, we must consider simple solutions for even complex applications. Rest assured, that people who don't like convention based configuration will be able to easily ignore it. I believe it will be very useful, especially for inserts, updates and deletes. Some simple queries will be made easier as well.

By default, iBATIS 3.0 will be configured by convention. Java 5 method signatures have enough information to generate the SQL and map all parameters and results. For example:

Employee getEmployee (int id);

This is enough information to generate:

SELECT id, firstName, lastName FROM Employee WHERE id = ?

The result class is equal to the method return type. Because the method result class is not a collection, we know we're dealing with "SELECT one" semantics. The table name is assumed to be the same as the result class. The parameter is assumed to be ID for "SELECT one" methods (only because Java lacks introspection on parameter names...C# can do this). We can override the default ID parameter if we need to using one of the other configuration options (annotation, XML or Java API). The returned columns are assumed to be equal to the class properties but can be overridden too (see "Options" below).

Collections work the same way:

List<Employee> listAllEmployees ();

In this case we generate:

SELECT id, firstName, lastName FROM Employee

We can tell it's a "SELECT many" because the method returns a collection. We know the WHERE clause is empty because there are no parameters.

If we want parameters for a "query by example", we can support something like this:

List<Employee> findEmployeesLike(Employee employee);

Assuming only lastName is set, all other reference types are null, and primitives are set to some invalid number like -1...we can generate something like:

SELECT id, firstName, lastName FROM Employee WHERE lastName = 'Begin'

Or a better convention by naming could be:

List<Employee> findEmployeeByLastNameAndFirstName (String last, String first);

This will yeild:

SELECT id, firstName, lastName FROM Employee WHERE lastName = 'Begin' AND firstName = 'Clinton'

In C# this will be very nice, because C# can introspect on parameter names. It's quite a bit cleaner, but the same idea:

// C#
    IList<Employee> FindEmployeesLike(string lastName, string firstName);
    
    // And in C# 3.0, we can use anonymous types:
    
    IList<Employee> FindEmployeesLike(object obj);
    //...
    IList<Employee> employees = mapper.FindEmployeesLike(new {LastName="Begin", FirstName="Clinton"});

Updates will work the same way:

void insertEmployee (Employee emp);
    void updateEmployee (Employee emp);
    void deleteEmployee (Employee emp);

We can also accept collections automatically:

int insertEmployee (List<Employee> emps); // returns rows inserted
    int updateEmployee (List<Employee> emps); // returns rows updated
    int deleteEmployee (List<Employee> emps); // returns rows deleted

The generator could be smart enough to code a single statement for mass deletes (and maybe even some updates).

관계

We may be able to support some simple relationships. Simply having a complex property type or collection property type on a class is enough information to identify the relationship. Class definitions are descriptive enough to infer the relationship, even though the reverse is not true (i.e. the data model is not descriptive enough).

표준 SQL? 그게 뭐지?

We're not going to go crazy with proprietary SQL support. In fact, we're not going to support it at all. We'll support the most standard SQL that works for most databases. This shouldn't be a problem considering the basic queries we're supporting in the convention based generation. Any crazy advanced SQL can be done by hand.

몇가지 설정가능한 옵션들

The following are some options we could support for SQL generation.

  • Translate JavaBean naming convention to UPPERCASE_UNDERSCORE database conventions.
  • Primary key includes table name.
  • Use SELECT * and lazy mapping for queries instead eager Class/Table mapping via Database Metadata at startup.
  • Use old WHERE clause join syntax
  • Default relationship loading to use join mapping or lazy loading

어노테이션으로 설정하기

Annotations are becoming more popular and many people are choosing it over XML for metaprogramming. Configuration should not be included in annotations, but much of what iBATIS puts in XML is not configuration.

So what is configuration and what is not? Current iBATIS XML files contain 3 things:

  • Configuration
  • Meta information
  • Code

Configuration is anything that changes when you change environments. For example, database connection information, transaction management etc. In some cases if you're deploying to different databases and use proprietary SQL, then the configuration might include selecting the SQL dialect. We can include such things in iBATIS 3.0, so you can code multiple dialects (by hand of course), give them names and configure it in the appropriate location – outside of your .java files. Configuration should not be in .java files.

Meta information includes things like Result Maps, Parameter Maps and Cache Models. This information changes how iBATIS maps your data and how the mappings behave.

Code includes SQL and the dynamic SQL elemenets.

So what should be in annotations?

For the most part, only meta information should be in annotations. Configuration should be left to properties files or XML. Code should be left to Java code or XML. Furthermore, annotations should probably be used only for simple meta information. Annotations (especially in Java) can become complex, unreadable and messy.

So, I'd recommend using Annotations only as a means to override convention based configuration. That is, use it to tweak the odd SQL statement and the odd Result Map or Parameter Map that needs a column or two remapped.

Once again, C# Attributes will look a lot better than Java Annotations for a few reasons:

  • C# supports multiline strings, which makes inline SQL coding a lot more pleasant.
  • C# supports multiple attributes of the same type. Java requires horrendous collection attributes.
  • C# supports attributes with both ordinal and named parameters, making code much cleaner and more terse.

That said, the following is an example of how some of these annotations might look in Java.

NOTE: You'll notice also that I've included inline result syntax, as well as inline parameters. This topic will be controversial since the syntax for results look more like the old syntax for parameters. Overall though, I think this is a really significant improvement and an important one. Some people may choose to ignore inline parameters or results, or both, but many people will use them exclusively in favour of annotations or XML. Also as a sidenote, parameters will always be named from now on. No more "?" ever. The names can be referenced with "decorator annotations" to change properties of the parameter (e.g. types) etc.

//
      // Simple select, string concatenation, inline results(!) and inline parameters
      //
    
    @Select("SELECT #id(EMP_ID:NUMERIC), #firstName(FIRST_NAME:VARCHAR), #lastName(LAST_NAME:VARCHAR) " +
    "FROM EMPLOYEE")
    List<Employee> selectAllEmployees();
    
    //
      // Alternative syntax using an array of strings instead of string concatenation...can be "smarter" than concatenation
      //
      @Select({"SELECT #id(EMP_ID:NUMERIC), #firstName(FIRST_NAME:VARCHAR), #lastName(LAST_NAME:VARCHAR) ",
    "FROM EMPLOYEE",
    "WHERE EMP_ID = @id"})
    Employee selectEmployee(int id);
    
    //
      // Inserts look as you might expect.  We can use getGeneratedKeys to get autogen key values, selectkey still supported
      //
      @Insert({"INSERT INTO EMPLOYEE (EMP_ID, FIRST_NAME, LAST_NAME)",
    "VALUES (@id, @firstName, @lastName)"})
    void insertEmployee(Employee emp);
    
    //
      // Nothing special about update
      //
      @Update({"UPDATE EMPLOYEE SET",
    "EMP_ID=@id(NUMERIC:IN), FIRST_NAME=@firstName(VARCHAR:IN), LAST_NAME=@lastName(VARCHAR:IN)"})
    void updateEmployee(Employee emp);
    
    //
      // Delete is obvious.
      //
      @Delete("DELETE EMPLOYEE WHERE EMP_ID = @id")
    void deleteEmployee(int id);

The following examples are more complicated, almost to the point that this amount of annotation should probably be moved to the XML. I imagine we'll make the annotations full featured, in that anything that can be done in XML will be possible with annotations. However, it will likely be our running recommendation that XML be preferred for complex or even just verbose cases. Some people may choose to ignore the recommendation, and we'll support them in that.

//
      // complex stuff
      //
    
    @ResultClass (Company.class)
    @ConstructorResults({
    @Result(property="id", column="C.COMP_ID"),
    @Result(property="name", column="C.NAME")
    })
    @PropertyResults({
    @Result(property="departments.id", column="D.DEPT_ID"),
    @Result(property="departments.name", column="D.NAME"),
    @Result(property="departments.employee.id", column="E.EMP_ID"),
    @Result(property="departments.employee.firstName", column="E.FIRST_NAME"),
    @Result(property="departments.employee.lastName", column="E.LAST_NAME")
    })
    @Collections ({
    @Collection(type=Department.class, property="departments", groupBy="id"),
    @Collection(type=Employee.class, property="departments.employees", groupBy="departments.id")
    })
    @Select("SELECT #id, #name, " +
    "#departments.id, #departments.name, " +
    "#departments.employees.id, #departments.employees.firstName, " +
    "#departments.employees.lastName " +
    "FROM COMPANY C, DEPARTMENT D, EMPLOYEE E " +
    "WHERE D.DEPT_ID = E.DEPT_ID " +
    "AND C.COMP_ID = D.COMP_ID")
    List<Company> selectAllCompaniesWithJoin();
    
    /*
    * NESTED QUERIES
    */
    
    @ResultClass (Company.class)
    @FieldResults({
    @Result(property="id", column="COMP_ID"),
    @Result(property="name", column="NAME")
    })
    @PropertyResults({
    @Result(property="departments",
    nestedQuery=@QueryMethod(type=CompanyMapper.class, methodName="getDepartmentsForCompany", parameters="id"))
    })
    @Select("SELECT #id, #name FROM COMPANY C ")
    List<Company> selectAllCompaniesWithNestedQueries();
    
    @ResultClass (Department.class)
    @PropertyResults({
    @Result(property="id", column="DEPT_ID"),
    @Result(property="name", column="NAME"),
    @Result(property="employees",
    nestedQuery=@QueryMethod(type=CompanyMapper.class, methodName="getEmployeesForDeparment", parameters="id"))
    })
    @Select("SELECT #id, #name FROM DEPARTMENT WHERE COMP_ID = @id ")
    List<Department> getDepartmentsForCompany(int id);
    
    @ResultClass (Employee.class)
    @PropertyResults({
    @Result(property="id", column="EMP_ID"),
    @Result(property="firstName", column="FIRST_NAME"),
    @Result(property="lastName", column="LAST_NAME")
    })
    @Select("SELECT #id, #firstName, #lastName FROM EMPLOYEE WHERE EMP_ID = @id ")
    List<Employee> getEmployeesForDepartment(int id);

XML로 설정하기

XML configuration will be somewhat familiar, however there is a lot of room for improvement. Most notably, we'll support the following:

  • Result and Parameter mappings to fields, constructor parameters and JavaBeans properties.
    • Yet again, C# will rule with regard to constructor parameters, because they can be named. Java will be limited to ordinal constructor parameter mappings.
  • Join mapping and groupBy (N+1 selects solution) can be made much less verbose. We can reduce the need to create sub-resultmaps as well as make the syntax more clearly describe what the intent is (see Collection element below).
  • All Result and Parameter Maps will be "auto-mapped" so only columns that are mismatched against their properties need to be described
    • this dates back to a heated discussion from years back about ignoring missing columns, and/or auto-map extra columns
  • Improved/Simplified typehandler implementation and possibly data type conversion filters
  • The biggest change will be that the XML files will sit beside their Mapper.class counterparts. So EmployeeMapper.xml will be loaded for EmployeeMapper.class in the same classpath by calling something like config.addMapper(EmployeeMapper.class).

Important: People who wish to ignore the new convention and annotation based configuration can do so without missing out on any features. We're not changing the paradigm, we're adding to it. We may even allow people to set their configuration level by specifying "Convention", "Annotation", "XML" or "Java". That is, if they set it to XML, no convention or annotation configuration will be attempted. But even by setting Annotation, XML will still be attempted. So there's definitely a priority order to the configuration levels. Obviously disabling Java API configuration will not be possible.

<Mapper>
    
    <ResultMap id="selectACompanyWithJoin" resultClass="Company">
    <Constructor column="C.COMP_ID"/>
    <Constructor column="C.NAME"/>
    
    <Property name="departments.id" column="D.DEPT_ID"/>
    <Property name="departments.name" column="D.NAME"/>
    <Property name="departments.employee.id" column="E.EMP_ID"/>
    <Property name="departments.employee.firstName" column="E.FIRST_NAME"/>
    <Property name="departments.employee.lastName" column="E.LAST_NAME"/>
    
    <Collection type="Department.class" property="departments" groupBy="id"/>
    <Collection type="Employee.class" property="departments.employees" groupBy="departments.id"/>
    </ResultMap>
    
    <Select id="selectACompanyWithJoin" parameters="id:int,order:string">
    SELECT
    #{id},
    #{name},
    #{departments.id},
    #{departments.name},
    #{departments.employees.id},
    #{departments.employee{,
    #{departments.employees.lastName}
    FROM COMPANY C
    INNER JOIN DEPARTMENT D ON C.COMP_ID = D.COMP_ID
    INNER JOIN EMPLOYEE E ON D.DEPT_ID = E.DEPT_ID
    WHERE
    C.COMP_ID = @{id}
    ORDER BY ${order}
    </Select>
    
    </Mapper>

You'll notice a number of changes, especially with the select statement itself. The markup has changed to:

* #{column} is an inline result column, optional, and redundant in the above examle -- but
    ultimately should be able to describe most any column mapping situation just like an external
    result map.  Many people won't like this, but it will make others very happy.  Sorry, it will
    be a bit confusing that that # is the token used for results now, rather than parameters, but
    it makes more sense.
    
    * @{property} is an inline parameter mapping to one of the provided parameters and its
    properties.  Much like inline parameters are today, very little difference other than the more
    common syntax using the @ symbol for parameters.
    
    * ${property} is an inline substring replacement and is the inspiration for the new syntax and
    does not change at all really.  At most we may introduce simple SQL injection protection,
    possibly optional.

자바 API로 설정하기

Without going into details, all of the other configuration approaches will make use of a clean, test driven, configuration API that is suitable to release and support as a public API. The entire framework should be configurable from this Java API. In general, it will look something like this:

Reader configFile = Resources.loadAsReader("MapperConfig.xml"); //contains DB connection information etc.
Configuration config = new Configuration(configFile);
config.addMapper (EmployeeMapper.class); // Configures by convention, annotation, and from EmployeeMapper.xml in the same path.
//
//...more config of statements, result maps, parameter maps and cache contexts....syntax unknown...
//
MapperFactory factory = config.buildMapperFactory();
EmployeeMapper empMapper = factory.getMapper(EmployeeMapper.class); //generics will allow us to avoid the cast

캐싱

  • Clinton says: Caching has never been very good in iBATIS...at least not as good as most ORMs, like TopLink and Hibernate. Unfortunately this is the reality with an SQL Mapping approach. Without introducing the concept of object identity and a number of other ORMish qualities, we can't support caching to the same level as the others. I don't believe caching is worth the loss of the core principles that make iBATIS different. So, if iBATIS caching is going to remain fairly basic, it should at least be easy. To date, cache configuration has been more work than it's worth. So, I believe we should make caching dead simple to configure, to achieve 80% of the value with 20% of the code. Any cases that require more advanced caching can write a custom cache above the persistence layer. For what it's worth, caching at the persistence layer is the least effective. Why? Because it's without user context. The best place to cache is at the web tier using a fragment caching approach that can understand more about user interactions and usage patterns that the persistence tier can never understand. In testing done with the Middleware Company years ago, we found business/context aware caches outperformed even the most advanced generic/holistic ORM caches every time.

So how is caching configured now?

As an annotation...

@CacheContext("Employee")
    public class EmployeeMapper {
    void insertEmployee(Employee emp);
    void updateEmployee(Employee emp);
    void deleteEmployee(Employee emp);
    Employee getEmployee(int id);
    List<Employee> findEmployeesLike(Employee emp);
    }

Or in XML...

<Mapper cacheContext="Employee">
    <Insert id="insertEmployee" ...> ... </Insert>
    <Update id="updateEmployee" ...> ... </Update>
    <Delete id="deleteEmployee" ...> ... </Delete>
    <Select id="getEmployee" ...> ... </Select>
    <Select id="findEmployeesLike" ...> ... </Select>
    </Mapper>

The Cache Context basically links a bunch of mapped statements together. The results of any statements that query for objects and return either collections or single objects will be cached similar to the way they are today – but without the highly verbose configuration. Any mapped statements in the cache context that modify data (insert/update/delete) will flush the cache.

If there's a need to flush on some other statement (like a weird stored proc that updates and queries or something), it can be overridden using a simple annotation.

@flushCache
    List<Employee> updateAndGetSpecialEmployees();

Or XML...

<Select id="findEmployeesLike" flushCache="true"...> ... </Select>

동적 SQL

Thanks to Brandon Goodin, iBATIS has one of the most powerful dynamic SQL facilities around. There's nothing else quite like it. And now I'm about to suggest we completely change it.

iBATIS has always been heavily XML based. However, as it turns out, XML sucks for procedural logic. Unfortunately the Dynamic SQL as it is today is procedural logic expressed in XML. In addition to the simple fact that XML sucks at this, there are also a number of syntactic issues with special characters. Normally we handle special characters by simply wrapping the entire statement in a CDATA section. But with Dynamic SQL, we can end up with relevant XML that we need within the CDATA section. This creates a mess of procedural logic, SQL fragments and CDATA sections. While still better than a lot of alternatives, it's simply not the best we can do.

As I said before, I consider the Dynamic SQL facility to be "code" (as opposed to configuration or meta information). Code should be in a general purpose programming language like Java. So, what I will suggest is that we rebuild the Dynamic SQL facility as a really nice Java API that is every bit as good as the Dynamic SQL facility. This API can be use inside very simple Dynamic SQL Source classes.

Now that everyone is confused, let's use an example:

OldWay.xml
<select id="dynamicGetAccountList" parameterClass="Account" resultMap="account-result" >
    select * from ACCOUNT
    <dynamic prepend="WHERE">
    <isNotNull prepend="AND" property="FirstName">
    ( ACC_FIRST_NAME = #FirstName#
    <isNotNull prepend="OR" property="LastName">
    ACC_LAST_NAME = #LastName#
    </isNotNull>
    )
    </isNotNull>
    <isNotNull prepend="AND" property="EmailAddress">
    ACC_EMAIL like #EmailAddress#
    </isNotNull>
    <isGreaterThan prepend="AND" property="Id" compareValue="0">
    ACC_ID = #Id#
    </isGreaterThan>
    </dynamic>
    order by ACC_LAST_NAME
    </select>
NewWay.java
// Warning: This is not well thought out.  There's a lot we can do with
    // anonymous inner classes, class initializers, generics and chained APIs (JMock)
    // or even scripting languages.  This is simply for the sake of argument.
    public class GetAccountListSQL extends SQLSource {
    public String getSQL(Object param) {
    Account acct = (Account) param;  // Generics might help eliminate this
        append("select * from ACCOUNT");
    prepend("WHERE"); //prepends before next append
        if (exists(acct.getEmailAddress())) {
    append("AND", "ACC_EMAIL like #EmailAddress#"); // append with prepend, first will be overridden
        }
    if (greaterThan(0,acct.getID())) {
    append("AND", "ACC_ID = #ID#");
    }
    prepend(); //clear prepend if any
        append ("order by ACCT_LAST_NAME");
    }
    }

This SQLSource can then be used with an annotation:

@SQLSource(GetAccountListSQL.class)
List<Accout> getAccountList(Account acct);

Or through XML:

<Select id="getAccountList" source="org.apache.GetAccountListSQL" ...> ... </Select>

I think this approach is much cleaner and keeps code where code should be. It also makes it possible to easily introduce new types of SQLSources so that SQL can be generated by scripting languages or (if anyone is really tied to it) even XML...

XML 파라미터와 결과 데이터

파라미터와 결과 데이터를 위해 원시타입, POJO, 자바빈즈 그리고 Map타입을 계속 지원할것이다. XML은 여전히 존재할것이지만 엄청나게 변경될것이다. 첫번째 클래스 타입처럼 지원되는 것 대신에, 모든 객체 타입을 위한 좀더 높은 레벨로 지원할것이다.

원시타입, POJO, 자바진즈 그리고 Map을 직렬화하기 위한 표준 iBATIS유틸리티를 소개하는 것처럼 간단한 것이라고 생각하거나 XStream과 같은 것을 사용하도록 사람들에게 알릴수 있다.

다음은 그 사항만을 가리키는 지극히 간단한 예제이다.

return As.XML(empMapper.getEmployee(As.Integer("<value>5</value>")));

기본적으로 XML은 어떤 객체 타입에서나 어떤 객체 타입으로의 좀더 높은 레벨의 변형이다. 존재하는 매핑 구문의 가장 상위에서 XML인터페이스를 원한다면 모든것을 xml타입으로 다시 매핑하지 않는다는 것이 이익이 된다.

iBATIS 3.0에서 제외되도록 추천된 기능들

  • DAO (2.x에서 이미 비권장되었음)
  • PaginatedList (2.x에서 이미 비권장되었음)
  • NullValue (이유: 보기 좋지 않고 혼동하기 쉽고 효과가 별로 없음)
  • MaxSessions, MaxTransactions, MaxRequests (session및 request는 새로운 설계에서는 존재하지 않을것이다. TX설정은 컨테이너의 역할이다.)

Timeline

It's way too early for a timeline. The best we can do is guess at how long it will be before we have a Beta quality release. Perhaps below each committer can give a gut check:

Brandon:
Brice:
Clinton: 6 - 12 months
Gilles:
Jeff:
Jon:
Larry: 12-18 months
Nathan:
Roberto:
Ron:
Sven:
Ted:


Site running on a free Atlassian Confluence Open Source Project License granted to OSS. Evaluate Confluence today.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.5.5 Build:#811 Jul 25, 2007) - Bug/feature request - Contact Administrators