Layer is logical seperation of the system. Typical 3-layered architecture comprises Presentation (UI) layer, Application (business) logic layer and Data Access or persistence layer.
Tier is physical seperation of the system. If the 3 layers are deployed on the same machine, then we have 1 tier and 3 layers. If the 3 layers are deployed on three seperate machines, then we have 3 tiers and 3 layers.
Sunday, 29 July 2012
Tuesday, 6 December 2011
Remove all .svn folders in Windows XP
It has been a tedious job to remove all the .svn folders in a project checked out from SVN repository. Thanks to http://www.iamatechie.com/remove-all-svn-folders-in-windows-xp-vista/, we now have an easy way.
Create a blank svnresistry.reg file (can be any name with .reg extension) on your desktop, edit it, and copy the following code snippet as its content.
Double click the reg file to execute it.
Then you can right click on the project folder, you will see a 'Delete SVN Folders' menu.
Just clicking on this menu will remove all the 'svn' folders under this directory recursively.
Create a blank svnresistry.reg file (can be any name with .reg extension) on your desktop, edit it, and copy the following code snippet as its content.
Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\DeleteSVN] @="Delete SVN Folders" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\DeleteSVN\command] @="cmd.exe /c \"TITLE Removing SVN Folders in %1 && COLOR 9A && FOR /r \"%1\" %%f IN (.svn) DO RD /s /q \"%%f\" \""
Double click the reg file to execute it.
Then you can right click on the project folder, you will see a 'Delete SVN Folders' menu.
Just clicking on this menu will remove all the 'svn' folders under this directory recursively.
Saturday, 3 December 2011
JPA one-to-many many-to-one relationship
many-to-one unidirectional mapping
Domain model
T_EMPLOYEE.MANAGER_MANA_ID is the foreign key referenced to T_MANAGER.MANA_ID
MANAGER_MANA_ID is the default foreign key column name. It is derived from class name + ‘_’ + primary key name (‘MANAGER’ + ‘_’+’MANA_ID’)
Test:
T_MANAGER
T_EMPLOYEE
Specify foreign key column name
Domain model
The name attribute of @JoinColumn (‘F_MANA_ID’) specifies the foreign key column name.
many-to-one bidirectional mapping
Domain model
Same as unidirectional mapping
Test:
Result:
Same as unidirectional mapping
T_MANAGER
T_EMPLOYEE
Retrieving the employees of a manager returns null.
Manager manager = em.find(Manager.class, 1);
System.out.println(manager.getEmployees());
This is because in a one-to-many relationship, the default fetch type at ONE side is LAZY.
Change the fetch type to EAGER, and we are able to retrieve the employees of a manager.
Add a new employee to a manager
Result:
Update an existing employee of a manager
Result:
Delete an existing employee of a manager
Result:
Bob is not deleted! We can see that simply removing the object from the set won’t actually remove the row from the database. So in this case, we need to invoke em.remove() to delete the row.
Result:
@Entity
@Table(name="T_MANAGER")
public class Manager {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="MANA_ID")
private int id;
@Column(name="MANA_NAME")
private String name;
}
@Entity
@Table(name="T_EMPLOYEE")
public class Employee {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="EMPL_ID")
private int id;
@Column(name="EMPL_NAME")
private String name;
@ManyToOne(cascade=CascadeType.PERSIST)
private Manager manager;
}
Domain model
T_EMPLOYEE.MANAGER_MANA_ID is the foreign key referenced to T_MANAGER.MANA_ID
MANAGER_MANA_ID is the default foreign key column name. It is derived from class name + ‘_’ + primary key name (‘MANAGER’ + ‘_’+’MANA_ID’)
Test:
@PersistenceContext(unitName="unit")
private EntityManager em;
private void save(){
Manager manager1 = new Manager("John");
Employee employee1 = new Employee("Bob");
Employee employee2 = new Employee("Tim");
Employee employee3 = new Employee("Mary");
employee1.setManager(manager1);
employee2.setManager(manager1);
employee3.setManager(manager1);
Manager manager2 = new Manager("Robert");
Employee employee4 = new Employee("Tom");
Employee employee5 = new Employee("Alex");
employee4.setManager(manager2);
employee5.setManager(manager2);
em.persist(employee1);
em.persist(employee2);
em.persist(employee3);
em.persist(employee4);
em.persist(employee5);
}
Result:T_MANAGER
T_EMPLOYEE
Specify foreign key column name
@OneToOne(cascade={CascadeType.PERSIST,
CascadeType.MERGE, CascadeType.REMOVE},
optional=false, fetch=FetchType.EAGER)
@JoinColumn(name="F_ADDR_ID", referencedColumnName="ADDR_ID")
private Address address;
Domain model
The name attribute of @JoinColumn (‘F_MANA_ID’) specifies the foreign key column name.
many-to-one bidirectional mapping
@Entity
@Table(name="T_MANAGER")
public class Manager {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="MANA_ID")
private int id;
@Column(name="MANA_NAME")
private String name;
@OneToMany(cascade={CascadeType.ALL}, mappedBy="manager")
private Set<Employee> employees;
}
@Entity
@Table(name="T_EMPLOYEE")
public class Employee {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="EMPL_ID")
private int id;
@Column(name="EMPL_NAME")
private String name;
@ManyToOne(cascade=CascadeType.PERSIST)
@JoinColumn(name="F_MANA_ID")
private Manager manager;
}
Domain model
Same as unidirectional mapping
Test:
@PersistenceContext(unitName="unit")
private EntityManager em;
private void save(){
Manager manager1 = new Manager("John");
Employee employee1 = new Employee("Bob");
Employee employee2 = new Employee("Tim");
Employee employee3 = new Employee("Mary");
Set<Employee> employees1 = new HashSet<Employee>();
employee1.setManager(manager1);
employee2.setManager(manager1);
employee3.setManager(manager1);
employees1.add(employee1);
employees1.add(employee2);
employees1.add(employee3);
manager1.setEmployees(employees1);
Manager manager2 = new Manager("Robert");
Employee employee4 = new Employee("Tom");
Employee employee5 = new Employee("Alex");
Set<Employee> employees2 = new HashSet<Employee>();
employees2.add(employee4);
employees2.add(employee5);
employee4.setManager(manager2);
employee5.setManager(manager2);
manager2.setEmployees(employees2);
em.persist(manager1);
em.persist(manager2);
}
Result:
Same as unidirectional mapping
T_MANAGER
Retrieving the employees of a manager returns null.
Manager manager = em.find(Manager.class, 1);
System.out.println(manager.getEmployees());
This is because in a one-to-many relationship, the default fetch type at ONE side is LAZY.
@OneToMany(cascade={CascadeType.ALL}, mappedBy="manager",
fetch=FetchType.EAGER)
private Set<Employee> employees;
Change the fetch type to EAGER, and we are able to retrieve the employees of a manager.
Add a new employee to a manager
Manager manager = em.find(Manager.class, 1);
Employee emp = new Employee("Adam");
emp.setManager(manager);
manager.getEmployees().add(emp);
em.merge(manager);
Result:
Update an existing employee of a manager
Manager manager = em.find(Manager.class, 1);
Employee employee = manager.getEmployees().iterator().next();
employee.setName("Josh");
em.merge(manager);
Result:
Delete an existing employee of a manager
Manager manager = em.find(Manager.class, 1);
Set<Employee> employees= manager.getEmployees();
for (Employee emp: employees){
if ("Bob".equalsIgnoreCase(emp.getName())){
employees.remove(emp);
break;
}
}
em.merge(manager);
Result:
Bob is not deleted! We can see that simply removing the object from the set won’t actually remove the row from the database. So in this case, we need to invoke em.remove() to delete the row.
Manager manager = em.find(Manager.class, 1);
Set<Employee> employees= manager.getEmployees();
for (Employee emp: employees){
if ("Bob".equalsIgnoreCase(emp.getName())){
employees.remove(emp);
em.remove(emp);
break;
}
}
em.merge(manager);
Result:
Saturday, 26 November 2011
JPA one-to-one relationship
Basic one-to-one unidirectional mapping
Domain model
T_CUSTOMER.ADDRESS_ADDR_ID is the foreign key referenced to T_ADDRESS.ADDR_ID
ADDRESS_ADDR_ID is the default foreign key column name. It is derived from class name + ‘_’ + primary key name (‘ADDRESS’ + ‘_’+’ADDR_ID’)
Since by default, no operations are cascaded, Address entity needs to be saved before Customer entity does.
The result:
T_CUSTOMER
T_ADDRESS
Add cascade attribute
Now Address entity does not need to be saved before Customer entity does.
Same for update and delete.
Specify whether association is optional, fetch type
Default value of optional attribute is true
Default value of fetch attribute is EAGER
Now if trying to save a customer without setting his address, an exception will be thrown.
Specify foreign key column name
Domain model
The name attribute of @JoinColumn (‘F_ADDR_ID’) specifies the foreign key column name.
The referenceColumnName attribute of @JoinColumn (‘ADDR_ID’) specifies the primary key of the table referenced to. It can be omitted.
One-to-one bidirectional mapping
Domain model
Same as unidirectional mapping
@Entity
@Table(name="T_CUSTOMER")
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="CUST_ID")
private int id;
@Column(name="CUST_NAME")
private String name;
@OneToOne
private Address address;
}
@Entity
@Table(name="T_ADDRESS")
public class Address {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="ADDR_ID")
private int id;
@Column(name="STREET")
private String street;
@Column(name="POSTCODE")
private String postcode;
}
Domain model
T_CUSTOMER.ADDRESS_ADDR_ID is the foreign key referenced to T_ADDRESS.ADDR_ID
ADDRESS_ADDR_ID is the default foreign key column name. It is derived from class name + ‘_’ + primary key name (‘ADDRESS’ + ‘_’+’ADDR_ID’)
Since by default, no operations are cascaded, Address entity needs to be saved before Customer entity does.
@PersistenceContext(unitName="unit")
private EntityManager em;
public void save(){
Address address = new Address("10 test st", "2912");
Customer customer = new Customer("Sun", address);
//Save address to database
em.save(address);
//Save customer to database
em.save(customer);
System.out.println("Customer saved");
}
The result:
T_CUSTOMER
T_ADDRESS
Add cascade attribute
@OneToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE,
CascadeType.REMOVE})
private Address address;
Now Address entity does not need to be saved before Customer entity does.
public void save(){
Address address = new Address("10 test st", "2912");
Customer customer = new Customer("Sun", address);
//Save customer to database
em.save(customer);
System.out.println("Customer saved");
}
Same for update and delete.
Specify whether association is optional, fetch type
@OneToOne(cascade={CascadeType.PERSIST,
CascadeType.MERGE, CascadeType.REMOVE},
optional=false, fetch=FetchType.EAGER)
private Address address;
Default value of optional attribute is true
Default value of fetch attribute is EAGER
Now if trying to save a customer without setting his address, an exception will be thrown.
@PersistenceContext(unitName="unit")
private EntityManager em;
public void save(){
Address address = new Address("10 test st", "2912");
Customer customer = new Customer("Sun", address);
//Save customer to database
em.save(customer);
System.out.println("Customer saved");
}
Specify foreign key column name
@OneToOne(cascade={CascadeType.PERSIST,
CascadeType.MERGE, CascadeType.REMOVE},
optional=false, fetch=FetchType.EAGER)
@JoinColumn(name="F_ADDR_ID", referencedColumnName="ADDR_ID")
private Address address;
Domain model
The name attribute of @JoinColumn (‘F_ADDR_ID’) specifies the foreign key column name.
The referenceColumnName attribute of @JoinColumn (‘ADDR_ID’) specifies the primary key of the table referenced to. It can be omitted.
One-to-one bidirectional mapping
@Entity
@Table(name="T_CUSTOMER")
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="CUST_ID")
private int id;
@Column(name="CUST_NAME")
private String name;
@OneToOne(cascade={CascadeType.ALL}, optional=false)
@JoinColumn(name="F_ADDR_ID")
private Address address;
}
@Entity
@Table(name="T_ADDRESS")
public class Address {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="ADDR_ID")
private int id;
@Column(name="STREET")
private String street;
@Column(name="POSTCODE")
private String postcode;
@OneToOne(mappedBy="address")
private Customer customer;
}
Domain model
Same as unidirectional mapping
@PersistenceContext(unitName="unit")
private EntityManager em;
public void save(){
Address address = new Address("10 test st", "2912");
Customer customer = new Customer("Sun", address);
address.setCustomer(customer);
//Save customer to database
em.save(customer);
System.out.println("Customer saved");
}
Friday, 25 November 2011
Basic JPA annotations
The simplest entity bean:
The table “STUDENT” generated:
Specify table name, column name, nullable, length and unique:
Constraints:
CREATE UNIQUE INDEX SQL110902022422451 ON T_STUDENT(STU_NAME);
ColumnDefinition overwrites length
The table “T_STUDENT” generated:
Integer column and BigDecimal column
The table “T_STUDENT” generated:
Specify precision and scale of BigDecimal
(The number 123.45 has a precision of 5 and a scale of 2)
The table “T_STUDENT” generated:
Date column
The table “T_STUDENT” generated:
Temporal annotation to specify the date type (date, time, timestamp)
The column “START_DATE” generated:
The value:
The column “START_DATE” generated:
The value:
The column “START_DATE” generated:
The value:
Specify default date, not insertable, not updateable
The table “T_STUDENT” generated:
Blob and Clob
The table “T_STUDENT” generated:
@Entity
public class Student {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
private String name;
}
The table “STUDENT” generated:
Specify table name, column name, nullable, length and unique:
@Entity
@Table(name="T_STUDENT")
public class Student {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="STU_ID")
private int id;
@Column(name="STU_NAME", nullable=false,
unique=true, length=30)
private String name;
}
Constraints:
CREATE UNIQUE INDEX SQL110902022422451 ON T_STUDENT(STU_NAME);
ColumnDefinition overwrites length
@Entity
@Table(name="T_STUDENT")
public class Student {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="STU_ID")
private int id;
@Column(name="STU_NAME", nullable=false,
unique=true, columnDefinition="VARCHAR(50)", length=30)
private String name;
}
The table “T_STUDENT” generated:
Integer column and BigDecimal column
@Entity
@Table(name="T_STUDENT")
public class Student {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="STU_ID")
private int id;
@Column(name="STU_NAME")
private String name;
@Column(name="AGE")
private Integer age;
@Column(name="MONEY")
private BigDecimal money;
}
The table “T_STUDENT” generated:
Specify precision and scale of BigDecimal
(The number 123.45 has a precision of 5 and a scale of 2)
@Entity
@Table(name="T_STUDENT")
public class Student {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="STU_ID")
private int id;
@Column(name="STU_NAME")
private String name;
@Column(name="AGE")
private Integer age;
@Column(name="MONEY", precision=5, scale=2)
private BigDecimal money;
}
The table “T_STUDENT” generated:
Date column
@Entity
@Table(name="T_STUDENT")
public class Student {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="STU_ID")
private int id;
@Column(name="STU_NAME")
private String name;
@Column(name="AGE")
private Integer age;
@Column(name="MONEY", precision=5, scale=2)
private BigDecimal money;
@Column(name="START_DATE")
private Date start;
}
The table “T_STUDENT” generated:
Temporal annotation to specify the date type (date, time, timestamp)
@Column(name="START_DATE") @Temporal(TemporalType.DATE) private Date start;
The column “START_DATE” generated:
The value:
@Column(name="START_DATE") @Temporal(TemporalType.TIME) private Date start;
The column “START_DATE” generated:
The value:
@Column(name="START_DATE") @Temporal(TemporalType.TIMESTAMP) //This is the default private Date start;
The column “START_DATE” generated:
The value:
Specify default date, not insertable, not updateable
@Entity
@Table(name="T_STUDENT")
public class Student {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="STU_ID")
private int id;
@Column(name="STU_NAME")
private String name;
@Column(name="START_DATE",
columnDefinition="DATE DEFAULT CURRENT_DATE",
insertable=false, updatable=false)
private Date start;
}
The table “T_STUDENT” generated:
Blob and Clob
@Entity
@Table(name="T_STUDENT")
public class Student {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="STU_ID")
private int id;
@Column(name="STU_NAME")
private String name;
@Lob
@Column(name="REPORT")
private String report;
@Lob
@Column(name="IMAGE")
private byte[] image;
}
The table “T_STUDENT” generated:
Monday, 21 November 2011
EJB Security configuration in Glassfish 3.1
Assume the MySQL data source has been set up. (If not, check Configure MySQL datasource in Glassfish 3.1)
The JNDI name is jdbc/mysql
Start Glassfish, go to Admin Console, http://localhost:4848
Configurations -> server-config -> Security -> Realms
Click on 'New' button
Name: jdbcRealm
Class Name: Select com.sun.enterprise.security.auth.realm.jdbc.JDBCRealm
JAAS Context: jdbcRealm
JNDI: jdbc/mysql
User Table: T_USER
User Name Column: username
Password Column: password
Group Table: T_GROUP
Group Name Column: groupname
Digest Algorithm: none
In the MySQL data source, create two tables: T_USER and T_GROUP
Insert data
Create a stateless session bean
Create a servlet
Edit web.xml under WEB-INF
Note: The value of <realm-name> (jdbcRealm) must match the Name field in Admin Console.
Edit sun-web.xml under WEB-INF
Note: The value of <role-name> must match the value of <role-name> under <security-role> in web.xml. The value of <group-name> must match the value of groupname column in T_GROUP table.
Test the servlet
http://localhost:8080/security-web/se
The console prints
User: sun
Saved
Close and open the browser again, login with the user 'ming'
The console prints
javax.ejb.EJBAccessException
Then try to login with an nonexistent user
The console prints
java.lang.SecurityException
The JNDI name is jdbc/mysql
Start Glassfish, go to Admin Console, http://localhost:4848
Configurations -> server-config -> Security -> Realms
Click on 'New' button
Name: jdbcRealm
Class Name: Select com.sun.enterprise.security.auth.realm.jdbc.JDBCRealm
JAAS Context: jdbcRealm
JNDI: jdbc/mysql
User Table: T_USER
User Name Column: username
Password Column: password
Group Table: T_GROUP
Group Name Column: groupname
Digest Algorithm: none
In the MySQL data source, create two tables: T_USER and T_GROUP
CREATE TABLE T_USER ( `username` VARCHAR(30) NOT NULL, `password` VARCHAR(30) NOT NULL, PRIMARY KEY (`username`) ) CREATE TABLE T_GROUP ( `username` VARCHAR(30) NOT NULL, `groupname` VARCHAR(30) NOT NULL, PRIMARY KEY (`username`) )
Insert data
insert into T_USER values (‘sun’, ‘123’); insert into T_USER values (‘ming’, ‘456’); insert into T_GROUP values (‘sun’, ‘adminGroup’); insert into T_GROUP values (‘ming’, ‘userGroup’);
Create a stateless session bean
@Stateless(name="securityManager")
@Local(SecurityManager.class)
public class SecurityManagerBean implements SecurityManager {
@Resource
private EJBContext context;
@RolesAllowed({"admin"})
public void save() {
System.out.println("User: "
+context.getCallerPrincipal().getName());
System.out.println("Save");
}
}
Create a servlet
public class SecurityServlet extends HttpServlet {
@EJB(beanName="securityManager")
private SecurityManager securityManager;
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
securityManager.save();
}
}
Edit web.xml under WEB-INF
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <security-role> <role-name>user</role-name> </security-role> <security-role> <role-name>admin</role-name> </security-role> <security-constraint> <web-resource-collection> <web-resource-name>all resources</web-resource-name> <url-pattern>/se</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> <http-method>HEAD</http-method> </web-resource-collection> <auth-constraint> <role-name>user</role-name> <role-name>admin</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>NONE</transport-guarantee> </user-data-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>jdbcRealm</realm-name> </login-config> <servlet> <servlet-name>se</servlet-name> <servlet-class>web.SecurityServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>se</servlet-name> <url-pattern>/se</url-pattern> </servlet-mapping> </web-app>
Note: The value of <realm-name> (jdbcRealm) must match the Name field in Admin Console.
Edit sun-web.xml under WEB-INF
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 8.1 Servlet 2.5//EN" "http://www.sun.com/software/appserver/dtds/sun-web-app_2_5-0.dtd"> <sun-web-app> <security-role-mapping> <role-name>user</role-name> <group-name>userGroup</group-name> </security-role-mapping> <security-role-mapping> <role-name>admin</role-name> <group-name>adminGroup</group-name> </security-role-mapping> </sun-web-app>
Note: The value of <role-name> must match the value of <role-name> under <security-role> in web.xml. The value of <group-name> must match the value of groupname column in T_GROUP table.
Test the servlet
http://localhost:8080/security-web/se
The console prints
User: sun
Saved
Close and open the browser again, login with the user 'ming'
The console prints
javax.ejb.EJBAccessException
Then try to login with an nonexistent user
The console prints
java.lang.SecurityException
Saturday, 19 November 2011
EJB use third party libraries
Assume the EJB project (containing the session bean, entity bean) is named ‘ejb3-jpa’, the EAR project is named ‘ejb3-ear’
An EJB session bean needs to use a third party library (e.g. Log4j)
Without any more changes, we will get a NoClassDefFoundError:
Caused by: java.lang.NoClassDefFoundError: org/apache/log4j/Logger
at session.StatelessSessionBean.<init>(StatelessSessionBean.java:13)
This is because the dependent jar file (Log4j.jar) won’t be automatically deployed to the server. So we need to manually add the dependent jar file to the EAR project.
Right click on ‘ejb3-ear’, and click on ‘Properties’
Click on ‘Add External JARs’
Add the ‘Log4j.jar’ file into the Java EE modules list
In ‘ejb3-jpa’ project, open the MANIFEST.MF file under META-INF
Enter the Log4j jar name following the Class-Path:
Manifest-Version: 1.0
Class-Path: log4j-1.2.14.jar
Now the StatelessSessionBean class is able to find the Log4j classes it is dependent on.
Appendix
Class loading rule
An EJB session bean needs to use a third party library (e.g. Log4j)
import org.apache.log4j.Logger;
@Stateless(name="stateless")
@Local(Session.class)
public class StatelessSessionBean implements Session {
private int result;
private Logger logger = Logger.getLogger(StatelessSessionBean.class);
@Override
public void add() {
logger.info("add");
result++;
}
@Override
public int get() {
return result;
}
}
Without any more changes, we will get a NoClassDefFoundError:
Caused by: java.lang.NoClassDefFoundError: org/apache/log4j/Logger
at session.StatelessSessionBean.<init>(StatelessSessionBean.java:13)
This is because the dependent jar file (Log4j.jar) won’t be automatically deployed to the server. So we need to manually add the dependent jar file to the EAR project.
Right click on ‘ejb3-ear’, and click on ‘Properties’
Click on ‘Add External JARs’
Add the ‘Log4j.jar’ file into the Java EE modules list
In ‘ejb3-jpa’ project, open the MANIFEST.MF file under META-INF
Enter the Log4j jar name following the Class-Path:
Manifest-Version: 1.0
Class-Path: log4j-1.2.14.jar
Now the StatelessSessionBean class is able to find the Log4j classes it is dependent on.
Appendix
Class loading rule
Subscribe to:
Posts (Atom)


































