Category: programming
A lesson of @Transactional
Recently, I needed to work with a Cursor for statistics.
<mapper ...> <select id="selectCorsor" resultOrdered="true"> </select> </mapper>
It didn’t make any difference.
@Mapper public interface SomeMapper { Cursor<Some> selectCursor(...); }
We need the @Transactional
annotation for working with Cursor
.
@Service public class SomeService { @Transactional public <R> R applyCursor( ..., final Function<Cursor<Some>, R> function) { return function.apply(someMapper.selectCursor(..)); } @Autowired private SomeMapper someMapper; }
Well the method worked as expected.
The problem arose when I added a method using the origin method.
public void acceptEach( ..., final Consumer<Some> consumer) { applyCursor( ..., cursor -> { cursor.forEach(consumer::accept); return null; } ); }
This auxiliary method was not annotated with @Transactional
and it didn’t work.
And I found @Transactional method calling another method without @Transactional anotation?.
The acceptEach
method was also required to be annotated with @Transactional
.
Using Cursors with MyBatis
There are queries that you shouldn’t map as List<T>
.
And the Cursor
comes to play.
@Service public class SomeService { public Cursor<Some> getCursor(...) { // returns the cursor? } }
Well it not a good way to do with it. The return value Cursor<Some>
won’t work because the session may already be finished when the method returns.
@Service public class SomeService { @Transactional public <R> R applyCursor( ..., Function<Cursor<Some>, R> function) { final Cursor<Some> cursor = ...; return function.apply(cursor); } }
Now we can add some other methods to do with it.
public <R> R applyIterator( ..., Function<Iterator<Some>, R> function) { applyCursor( ..., cursor -> function.apply(cursor.iterator()) ); }
What about a Spliterator
?
public <R> R applySpliterator( ..., Function<Spliterator<Some>, R> function) { applyIterator( ..., iterator -> { int characteristics = Spliterator.DISTINCT | Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.ORDERED; Spliterator<CsEventAssociate> spliterator = Spliterators.spliteratorUnknownSize( iterator, characteristics); return function.apply(spliterator); } ); }
And the Stream
?
public <R> R applyStream( ..., Function<Stream<Some>, R> function) { return applySpliterator( ..., spliterator -> function.apply(StreamSupport.stream(spliterator, false)) ); }
Using Live Templates with IntelliJ IDEA
In Editor > Live Templates
Choose Abbreviation:
author
Fill the Template text:
@author $NAME$ <$USER$_at_company.com> on $DATE$
Edit variables -> Expressions
NAME -> "Jin Kwon" USER -> user() DATE -> date("yyyy-MM-dd")
Getting a Substring in MySQL
I just learned how to get substring of specific range using LOCATE() and SUBSTRING().
Let’s say we have email addresses and want to get username part from them.
Step 1 Locate the index of ‘@’ character
LOCATE('@', email)
Step 2 Get substring using it.
SUBSTRING(email, 1, LOCATE('@', email) - 1)
Note that the starting index is 1 and the the len part must be 1 minus the location.
Wait.
There is a function exactly does this. SUBSTRING_INDEX().
SUBSTRING_INDEX(email, '@', 1)
Injecting Property Values into EJBs from Database
references
Property.java
@Entity @NamedQueries({ @NamedQuery(name = "Property.find", query = "SELECT p FROM Property AS p WHERE p.name = :name") }) public class Property { @Basic(optional = false) @Column(name = "NAME", nullable = false, unique = true) @NotNull private String name; @Basic(Optional = false) @Column(name = "VALUE_", nullable = false) @NotNull private String value = ""; }
find
public static Property findNamed(final EntityManager entityManager, final String name) { final TypedQuery typedQuery = entityManager.createNamedQuery("Property.find", Property.class); typedQuery.setParameter("name", name); try { return typedQuery.getSingleResult(); } catch (final NoResultException nre) { return null; } } public static Property findCriteria(final EntityManager entityManager, final String name) { final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); final CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Property.class); final Root<Property> property = criteriaQuery.from(Property.class); criteriaQuery.select(property); criteriaQuery.where( criteriaBuilder.equal(property.get(Property_.name), name)); final TypedQuery typedQuery = entityManager.createQuery(criteriaQuery); try { return typedQuery.getSingleResult(); } catch (final NoResultException nre) { return null; } } public static Property find(final EntityManager entityManager, final String name) { if (current().nextBoolean()) { return findNamed(entityManager, name); } return findCriteria(entityManager, name); }
PropertyValue.java
@Qualifier @Retention(RUNTIME) @Target({FIELD, METHOD, PARAMETER, TYPE}) public @interface PropertyValue { @Nonbinding String name() default ""; @Nonbinding boolean optional() default true; }
PropertyFactory.java
@Dependent public class PropertyValueFactory { @Produces @PropertyValue public String producePropertyValue(final InjectionPoint injectionPoint) { final PropertyValue annotation = injectionPoint.getAnnotated().getAnnotation(PropertyValue.class); final String name = annotation.name(); final boolean optional = annotation.optional(); final Property property = Property.find(entityManger, name); if (property == null && !optional) { throw new InjectionException("property not found; name= " + name); } return ofNullable(property).map(Property::getValue).orElse(null); } public void diposePropertyValue( @Disposes @PropertyValue final String value) { // empty @PersistenceContext private EntityManager entityManger; }
StorageObjectServiceFtp.java
@Stateless public class StorageObjectServiceFtp extends StorageObjectService { @Inject @PropertyValue(name = "ftp.hostname", optional = false) private String hostname; @Inject @PropertyValue(name = "ftp.username", optional = false) private String username; @Inject @PropertyValue(name = "ftp.password", optional = false) private String password; @Inject @PropertyValue(name = "ftp.rootpath", optional = true) private String rootpath; }
Making NetBeans detects JAVA_HOME
NetBeans’ configuration file contains following line.
# etc/netbeans.conf # netbeans_jdkhome="C:\Program Files\Java\jdkX.Y.Z_SOME" #
When you upgrade JDK, and possibly uninstall the older one, NetBeans starts asking about JDK.
Just comment the line out.
Basic Java EE with Apache Maven
some-parent
다른 모듈들에서 사용할 dependency/plugin 등을 정리하는 모듈이다. java-ee-api
와 derby
, h2
등의 메모리 DB, 그리고, eclipselink
, hibernate-core
, openejb-core
등이 정의되었다.
<?xml version="1.0" encoding="UTF-8"?> <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>some.group</groupId> <artifactId>some-parent</artifactId> <version>0.1.0-SNAPSHOT</version> <packaging>pom</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <version.org.apache.tomee>7.0.2</version.org.apache.tomee> <version.org.eclipse.persistence>2.6.4</version.org.eclipse.persistence> <version.org.glassfish.jersey>2.25</version.org.glassfish.jersey> <version.org.glassfish.jersey.security>${version.org.glassfish.jersey}</version.org.glassfish.jersey.security> <version.org.glassfish.jersey.test-framework>${version.org.glassfish.jersey}</version.org.glassfish.jersey.test-framework> <version.org.wildfly.swarm>2017.1.1</version.org.wildfly.swarm> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.8</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.193</version> </dependency> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.5</version> </dependency> <dependency> <groupId>org.apache.derby</groupId> <artifactId>derby</artifactId> <version>10.13.1.1</version> </dependency> <dependency> <groupId>org.apache.tomee</groupId> <artifactId>openejb-core</artifactId> <version>${version.org.apache.tomee}</version> </dependency> <dependency> <groupId>org.eclipse.persistence</groupId> <artifactId>eclipselink</artifactId> <version>${version.org.eclipse.persistence}</version> </dependency> <dependency> <groupId>org.gavaghan</groupId> <artifactId>geodesy</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>org.glassfish</groupId> <artifactId>javax.el</artifactId> <version>3.0.1-b08</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.2.6.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.4.0.CR1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.22</version> </dependency> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>6.10</version> </dependency> </dependencies> </dependencyManagement> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>2.19.1</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <version>2.10.4</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-pmd-plugin</artifactId> <version>3.7</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-project-info-reports-plugin</artifactId> <version>2.9</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.19.1</version> </plugin> <plugin> <groupId>org.bsc.maven</groupId> <artifactId>maven-processor-plugin</artifactId> <version>3.2.0</version> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <version>2.7</version> </plugin> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.7.8</version> </plugin> </plugins> </pluginManagement> </build> </project>
some-entities
Persistence-Unit 에 해당하는 모듈이다. 중요한 점으로 이 묘듈은 persistence.xml
파일을 포함하지 않는다는 것이다. persistence.xml
파일은 최종 application 에서 정의한다.
javaee-api
우선적으로 필요한 의존성은 javaee-api
이다. provided
scope로 선언한다.
... <groupId>some</groupId> <artifactId>some-entities</artifactId> <version>0.1.0-SNAPSHOT</version> <packaging>jar</packaging> ... <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <scope>provided</scope> </dependency> </dependencies> ... </project>
BaseEntity.java
필요에 따라서 다른 entity들이 상속할 수 있는 기본클래스를 선언할 수도 있다.
@MappedSuperclass public abstract class BaseEntity implements Serializable { // ... @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = COLUMN_NAME_ID, nullable = false, updatable = false) @XmlAttribute private Long id; // ... }
metamodel generation
JPA Metamodel 들을 생성한다. maven-processor-plugin
과 hibernate-jpamodelgen
을 사용한다.
... <build> ... <plugins> ... <plugin> <groupId>org.bsc.maven</groupId> <artifactId>maven-processor-plugin</artifactId> <executions> <execution> <id>generate-metamodels</id> <goals> <goal>process</goal> </goals> <phase>generate-sources</phase> <configuration> <processors> <processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor> </processors> </configuration> </execution> </executions> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-jpamodelgen</artifactId> <version>5.2.6.Final</version> </dependency> </dependencies> </plugin> ... </plugins> ... </build> ...
src/test/resources/META-INF/persistence.xml
단위시험을 위해 in-memory database를 이용하는 persistence.xml
파일을 준비하자.
<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="somePU" transaction-type="RESOURCE_LOCAL"> <provider>${provider}</provider> <class>com.mycompany.some.Some</class> ... <validation-mode>CALLBACK</validation-mode> <properties> <property name="javax.persistence.jdbc.driver" value="${javax.persistence.jdbc.driver}"/> <property name="javax.persistence.jdbc.url" value="${javax.persistence.jdbc.url}"/> <property name="javax.persistence.schema-generation.database.action" value="create"/> </properties> </persistence-unit> </persistence>
validation-mode
를 CALLBACK
으로 설정했으므로 validation framework을 추가해 줘야 한다.
... <dependency> <groupId>org.glassfish</groupId> <artifactId>javax.el</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <scope>test</scope> </dependency> ...
자 이제 h2
, derby
등의 database와 eclipselinke
, hibernate-core
등의 JPA provider 등을 선택적으로 사용할 수 있는 profile 들을 작성한다.
<?xml version="1.0" encoding="UTF-8"?> <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"> ... <profiles> <profile> <id>derby</id> <activation> <activeByDefault>true</activeByDefault> </activation> <properties> <javax.persistence.jdbc.driver>org.apache.derby.jdbc.EmbeddedDriver</javax.persistence.jdbc.driver> <javax.persistence.jdbc.url>jdbc:derby:memory:someDB;create=true</javax.persistence.jdbc.url> </properties> <dependencies> <dependency> <groupId>org.apache.derby</groupId> <artifactId>derby</artifactId> <scope>test</scope> </dependency> </dependencies> </profile> <profile> <id>h2</id> <properties> <javax.persistence.jdbc.driver>org.h2.Driver</javax.persistence.jdbc.driver> <javax.persistence.jdbc.url>jdbc:h2:mem:someDB</javax.persistence.jdbc.url> </properties> <dependencies> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>test</scope> </dependency> </dependencies> </profile> <profile> <id>eclipselink</id> <activation> <activeByDefault>true</activeByDefault> </activation> <properties> <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> </properties> <dependencies> <dependency> <groupId>org.eclipse.persistence</groupId> <artifactId>eclipselink</artifactId> <scope>test</scope> </dependency> </dependencies> </profile> <profile> <id>hibernate</id> <properties> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> </properties> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <scope>test</scope> </dependency> </dependencies> </profile> </profiles> </project>
기본적으로 derby
와 eclipselink
를 activeByDefault
로 설정하였다. 다음과 같이 profile을 바꿔가면서 시험할 수 있다.
$ mvn -Ph2,eclipselink test $ mvn -Pderby,hibernate test
some-services
some-resources
RESTful HTTP DELETE?
DELETE
method 의 응답으로 어떤 값을 사용해야 할까? 하고 갑자기 궁금하던 중, 같은 논점에 대해, 상당양의 검색결과를 얻었다.
일단 idempotence 에 대한 내용은 RFC 2616 / 9.1.2 Idempotent Methods 에서 알아볼 수 있다.
RFC 의 내용은 그리 논란거리가 아니지만 이게 REST(ful) 동네로 넘어오면서 의견이 분분해지는 것 같다.
debate
논점을 좀 간단하게 하자면, 204
만 써야 하는가 아니면 404/410
등도 같이 써야 한다는 것인데…
개인적으로 204
를 사용하는 것이 옳다고 본다. 4xx
을 써도 된다는 의견들이 잘못됐다는 것은 아니지만 4xx
을 사용해야 한는 경우는 그것보다 더 넓은 의미와 상황을 포함한다.
Overriding JTA into RESOUCE-LOCAL while Unit Testing
references
JTA
Let’s say you have a very simple persistence.xml looks like this.
<persistence-unit name="somePU" transaction-type="JTA"> <jta-data-source>jdbc/someDS</jta-data-source> <exclude-unlisted-classes>false</exclude-unlisted-classes> <properties> <property name="javax.persistence.validation.mode" value="CALLBACK" /> </properties> </persistence-unit>
RESOUCE-LOCAL
You can, maybe you should, override it for unit testing. Following codes uses EclipseLink dependent API.
final Map<String, String> properties = new HashMap<>(); properties.put(PersistenceUnitProperties.TRANSACTION_TYPE, PersistenceUnitTransactionType.RESOURCE_LOCAL.name()); properties.put(PersistenceUnitProperties.JDBC_DRIVER, "org.apache.derby.jdbc.EmbeddedDriver"); properties.put(PersistenceUnitProperties.JDBC_URL, "jdbc:derby:memory:testDB;create=true"); properties.put(PersistenceUnitProperties.DDL_GENERATION, PersistenceUnitProperties.CREATE_ONLY); properties.put(PersistenceUnitProperties.LOGGING_LEVEL, Level.FINE.getName()); properties.put(PersistenceUnitProperties.TARGET_SERVER, TargetServer.None); ENTITY_MANAGER_FACTORY = createEntityManagerFactory("somePU", properties);