Professional Documents
Culture Documents
Best Practices
Carol McDonald
Java Architect
1
Agenda
>Entity Manager
>Persistence Context
>Entities
>Schema & Queries
>Transaction
2
EntityManager
set of entities
API for managing entities managed by
Entity Manager
EntityManager
persist()
remove()
Servlets
refresh()
EJBs merge()
Java app find()
createQuery()
createNamedQuery()Persistence
contains() Context
flush()
3
Catalog Java EE Application
Registration Application
Managed Bean Entity Class
Item DB
ManagedBean Catalog
Session Bean
JSF Components
4
S t a t e l e s s S e s s io n
B e a n A n n o t a t io n
@Stateless
public class Catalog implements CatalogService {
@PersistenceContext(unitName=”PetCatalogPu”)
EntityManager em;
@TransactionAttribute(NOT_SUPPORTED)
public List<Item> getItems(int firstItem,
int batchSize) {
Query q = em.createQuery
("select i from Item as i");
q.setMaxResults(batchSize);
q.setFirstResult(firstItem);
List<Item> items= q.getResultList();
return items;
}
} 5
Catalog Spring JPA Application
Registration Application
Managed Bean Entity Class
Item DB
ItemController
Catalog
Spring Bean
JSF Components
Spring
Framework
6
Spring with JPA
Component Stereotype
@Repository Spring transactions use aop
@Transactional
public class CatalogDAO implements CatalogService {
@PersistenceContext(unitName="PetCatalogPu")
private EntityManager em;
@Transactional(readOnly=true)
public List<Item> getItems(int firstItem,int batchSize) {
Query q =
em.createQuery("select object(o) from Item as o");
q.setMaxResults(batchSize);
q.setFirstResult(firstItem);
List<Item> items= q.getResultList();
return items;
}
7
Container vs Application Managed
Container managed entity managers (EJB, Spring Bean,
Seam component)
• Injected into application
• Automatically closed
• JTA transaction – propagated
Application managed entity managers
> Used outside of the JavaEE 5 platform
> Need to be explicitly created
● Persistence.createEntityManagerFactory()
> RESOURCE_LOCAL transaction – not propagated
> Need to explicitly close entity manager
8
Agenda
>Entity Manager
>Persistence Context
>Entities
>Queries
>Transaction
9
Persistence Context
set of entities
managed by
• Persistence context acts
Entity Manager
as a first level cache for
entities EntityManager
• Two types of persist()
persistence context remove()
> Transaction scoped refresh()
> Extended scoped merge()
persistence context find()
createQuery()
createNamedQuery()Persistence
contains() Context
flush()
10
Level1 and Level2 caches
L2 Cache
(Shared Cache)
Entity managers for a specific PersistenceUnit on a given Java Virtual Machine (JVM™)
The terms “Java Virtual Machine” and “JVM” mean a Virtual Machine for the Java™ Platform. Source:http://weblogs.java.net/blog/guruwons/
archive/2006/09/understanding_t.html
11
Entity Lifecycle
new()
New Detached
Entity Entity
Updates PC ends
persist()
Merge()
Persistence Context
Managed find()
Managed Managed
Entity
Entity Entity
Detached entity
}
13
Scope of Identity
@Stateless public ShoppingCartBean implements ShoppingCart {
}
14
Persistence Context
• Two types of persistence context
• Transaction scoped
> Used in stateless components
> Typically begins/ends at request entry/exit points
respectively
• Extended scoped persistence context
15
Persistence Context Propagation
16
Persistence Context Propagation
Check Out
New Persistence Persistence Context
Context Propagated
Shopping PC
1. Update Inventory PC
Cart Inventory Service
TX_REQUIRED TX_REQUIRED
Order PC
2. Create Order Service
Transaction
TX_REQUIRED
Attributes
18
AuditServiceBean
@Stateless
public class AuditServiceBean implements AuditService {
@PersistenceContext
private EntityManager em; NEW
PC !
@TransactionAttribute(REQUIRES_NEW)
public void logTransaction2(int id, String action) {
LogRecord lr = new LogRecord(id, action);
em.persist(lr);
}
19
Declarative Transaction Management
Example 2
Check Out
New Persistence Persistence Context
Context Propagated
Shopping PC
1. Update Inventory PC
Cart Inventory Service
REQUIRED REQUIRED
Audit PC2
2. log transaction Service
Transaction
REQUIRES_NEW
Attributes
NEW
PC ! 20
Persistence Provider PC Transaction Features
21
Conversation with detached entity
Conversation
22
Conversation with detached entity
@Stateless public ShoppingCartBean implements ShoppingCart {
@PersistenceContext EntityManager entityManager;
23
Types of Persistence Context
• Persistence Context
> lifetime maybe transaction-scoped or extended
• Transaction-scoped persistence context
• Extended persistence context
> spans multiple transactions
24
Conversation with Exented Persistence Context
Conversation
Persistence Context
Managed Managed Managed
Entity Entity Entity
25
Extended Persistence Context
@Stateful public class OrderMgr {
//Specify that we want an EXTENDED
@PersistenceContext(type=PersistenceContextType.EXTENDED)
EntityManager em;
//Cached order
private Order order;
//create and cache order
public void createOrder(String itemId) {
//order remains managed for the lifetime of the bean
Order order = new Order(cust);
em.persist(order);
} Managed entity
public void addLineItem(OrderLineItem li){
order.lineItems.add(li);
}
Managed entity
26
Extended Persistence Context
@Stateful public class DeptMgr {
@PersistenceContext(type=PersistenceContextType.EXTENDED)
EntityManager em;
private Department dept;
@TransactionAttribute(NOT_SUPPORTED)
public void getDepartment(int deptId) {
dept = em.find(Department.class,deptId);
}
@TransactionAttribute(NOT_SUPPORTED)
public void addEmployee(int empId){
emp = em.find(Employee.class,empId);
dept.getEmployees().add(emp);
emp.setDepartment(dept);
}
@Remove
@TransactionAttribute(REQUIRES_NEW)
public void endUpdate(int deptId) {
dept = em.find(Department.class,deptId);
} 27
Persistence Context-Transactional vs. Extended
@Stateless
public class OrderMgr implements OrderService {
@Stateful
public class OrderMgr implements OrderService {
@PersistenceContext(type = PersistenceContextType.EXTENDED))
EntityManager em;
// Order is cached Managed entity
Order order
public void addLineItem(OrderLineItem li){
No em.find invoked
// No em.find invoked for the order object
order.lineItems.add(li);
}
28
Persistence Context Micro Benchmark
• Micro benchmark with lots of lookups
• Persistence context is caching entities
120% Extended
Transactional
100%
80%
60%
40%
20%
0%
tx/sec
Source: Internal benchmarks
29
SEAM Conversations
@Name("shopper")
@Scope(CONVERSATION)
public class BookingManager { SEAM injected
30
Concurrency and Persistence Context
same entity
Object Identity
only one manage entity
in PC represents a row
Data source
31
Optimistic versus Pessimistic Concurrency
• Optimistic Concurrency
> Pros—No database locks held
> Cons—Requires a version attribute in schema
● user or app must refresh and retry failed updates
> Suitable when application has few parallel updates
• Pessimistic Concurrency
> Lock the row when data is read in
● database locks the row upon a select
● (SELECT . . . FOR UPDATE [NOWAIT])
> Pros—Simpler application code
> Cons—Database locks
● limits concurrent access to the data = scalability
● May cause deadlocks
● Not in JPA 1.0 (vendor specific), supported in JPA 2.0
> Suitable when application has many parallel updates
32
Preventing Parallel Updates
use @Version for optimistic locking
public class Employee { Can be int, Integer,
@ID int id; short, Short, long,
Long, Timestamp
@Version int version;
...
Used by persistence manager , Results in following SQL
“UPDATE Employee SET ..., version = version + 1
WHERE id = ? AND version = readVersion”
Version Updated when transaction commits, merged or
acquiring a write lock
OptimisticLockException if mismatch
Not used by the application!
33
Preventing Parallel Updates – 1
tx1.begin(); tx2.begin();
//Joe's employee id is 5 //Joe's employee id is 5
//e1.version == 1 //e1.version == 1
e1 = findPartTimeEmp(5); e1 = findPartTimeEmp(5);
//Joe's current rate is $9
e1.raise(2);
tx1.commit();
//e1.version == 2 in db //Joe's current rate is $9
Time
//Joe's rate is $11
if(e1.getRate() < 10)
e1.raise(5);
//e1.version == 1 in db?
tx2.commit();
//Joe's rate is $14
//OptimisticLockException
34
Preventing Stale Data JPA 1.0
35
Preventing Stale Data
tx1.begin(); tx2.begin();
d1 = findDepartment(dId);
e1 = findEmp(eId);
//d1's original name is d1 = e1.getDepartment();
//”Engrg” em.lock(d1, READ);
d1.setName(“MarketEngrg”); if(d1's name is “Engrg”)
e1.raiseByTenPercent();
tx1.commit();
36
Preventing Parallel Updates – 2
Write lock prevents parallel updates
tx1.begin(); tx2.begin();
d1 = findDepartment(dId);
e1 = findEmp(eId);
d1 = e1.getDepartment();
em.lock(d1, WRITE);
//version++ for d1
//d1's original name is em.flush();
//”Engrg” if(d1's name is “Engrg”)
Time
d1.setName(“MarketEngrg”); e1.raiseByTenPercent();
tx1.commit();
//tx rolls back
tx2.commit();
37
JPA 2.0 Locks
38
JPA 2.0 Locking
//Read then lock:
Account acct = em.find(Account.class, acctId);
// Decide to withdraw $100 so lock it for update
em.lock(acct, PESSIMISTIC); Lock after read, risk
int balance = acct.getBalance(); stale, could cause
OptimisticLock
acct.setBalance(balance - 100);
Exception
39
JPA 2.0 Locking
●Trade-offs:
●lock earlier : risk bad scalability, deadlock
●Lock later : risk stale data for update, get optimistic lock exception
40
L2 cache shared across transactions and users
L2 Cache (EntityManagerFactory)
(Shared Cache)
Entity managers for a specific PersistenceUnit on a given Java Virtual Machine (JVM™)
41
Second-level Cache
• L2 Cache shares entity state across various
persistence contexts
> If caching is enabled, entities not found in persistence
context, will be loaded from L2 cache, if found
• Best for read-mostly classes
• L2 Cache is Vendor specific
> Java Persistence API 1.0 does not specify L2 support
> Java Persistence API 2.0 has basic cache operations
> Most persistence providers-- Hibernate, EclipseLink,
OpenJPA ... provide second-level cache(s)
42
L2 Cache
same entity
not shared
44
L2 Caching
• Configure L2 caching for entities that are
> read often
> modified infrequently
> Not critical if stale
• protect any data that can be concurrently modified with
a locking strategy
> Must handle optimistic lock failures on flush/commit
> configure expiration, refresh policy to minimize lock
failures
• Configure Query cache
> Useful for queries that are run frequently with the
same parameters, for not modified tables
45
JPA 2.0 Shared Cache API
• entity cache shared across persistence unit
> Accessible from EntityManagerFactory
• Supports only very basic cache operations
> Can be extended by vendors
public class Cache {
//checks if object is in IdentityMap
public boolean contains(Class class, Object pk);
// invalidates object in the IdentityMap
public void evict(Class class, Object pk);
public void evict(Class class); // invalidates the class in the IdentityMap.
public void evictAll(); // Invalidates all classes in the IdentityMap
}
46
EclipseLink Caching Architecture
EntityManager EntityManager
EclipseLink caches
Entities in L2, Hibernate
Factory does not
L1 Cache
Server
PC Cache
47
EclipseLink Extensions - L2 Caching
• Default: Entities read are L2 cached
• Cache Configuration by Entity type or Persistence
Unit
> You can disable L2 cache
• Configuration Parameters
> Cache isolation, type, size, expiration, coordination,
invalidation,refreshing
> Coordination (cluster-messaging)
● Messaging: JMS, RMI, RMI-IIOP, …
● Mode: SYNC, SYNC+NEW, INVALIDATE, NONE
48
EclipseLink Mapping Extensions Type=
Full: objects never
@Entity flushed unless deleted
@Table(name="EMPLOYEE") or evicted
@Cache ( weak: object will be
type=CacheType.WEAK, garbage collected if not
isolated=false, referenced
expiry=600000,
alwaysRefresh=true,
disableHits=true, =true
coordinationType=INVALIDATE_CHANGED_OBJECTS disables L2 cache
)
public class Employee implements Serializable {
...
}
@Cache
● type, size,isolated, expiry, refresh, cache
usage, coordination
● Cache usage and refresh query hints
49
Hibernate L2 Cache
• Hibernate L2 cache is not configured by default
• Hibernate L2 does not cache Entities. Hibernate caches Id and
state
• Hibernate L2 cache is pluggable
> EHCache, OSCache, SwarmCacheProvider (JVM)
> JBoss TreeCache Cluster
> Can plug in others like Terracotta
Cache Concurrency Strategy
Cache Type Read-only Read-write Transactional
EHCache memory, disk Yes Yes
OSCache memory, disk Yes Yes
SwarmCache clustered Yes
JBoss Cache clustered Yes Yes
50
Hibernate L2 Cache
not configured by default
<!-- optional configuration file parameter -->
net.sf.ehcache.configurationResourceName=/name_of_configuration_resource
@Entity
@Cache(usage =
CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public Class Country {
private String name; Cache Concurrency Strategy
... must be supported by cache provider
}
51
OpenJPA L2 Caching
• OpenJPA L2 caches object data and JPQL query
results
• Updated when data is loaded from database and
after changes are successfully committed
• For cluster caches are notified when changes are
made
• Can plug in implementations, such as Tangosol’s
Coherence product
• several cache eviction strategies:
> Time-to-live settings for cache instances
@Entity @DataCache(timeout=5000)
public class Person { ... }
52
Agenda
>Entity Manager
>Persistence Context
>Entities
>Schema and Queries
>Transaction
53
Maintaining Relationship
• Application bears the responsibility of maintaining
relationship between objects
deptA.getEmployees().add(e3);
Before After
Employee1 Employee1
Department A Department A
Employee2 Employee2
Inconsistent!
54
Example – Domain Model
55
Example – Managing Relationship
INCORRECT
public int addNewEmployee(...) {
Employee e = new Employee(...);
Department d = new Department(1, ...);
e.setDepartment(d);
//Reverse relationship is not set
em.persist(e);
em.persist(d);
return d.getEmployees().size();
}
56
Example – Managing Relationship
CORRECT
public int addNewEmployee(...) {
Employee e = new Employee(...);
Department d = new Department(1, ...);
e.setDepartment(d);
d.getEmployees().add(e);
em.persist(e);
em.persist(d);
return d.getEmployees().size();
}
57
Navigating Relationships
Data fetching strategy
• EAGER – immediate
• LAZY – loaded only when needed
• LAZY is good for large objects and/or with
relationships with deep hierarchies
58
Lazy loading and JPA
@Entity public class Department {
@Id private int id;
@OneToMany(mappedBy = "dept")
private Collection<Employee> emps ;
...
}
• Default FetchType is LAZY for 1:m and m:n relationships
> benefits large objects and relationships with deep hierarchies
• However for use cases where data is needed can cause
n+1 selects
• LAZY – N + 1 problem:
59
Lazy loading and JPA
@Entity public class Department {
@Id private int id;
@OneToMany(mappedBy = "dept", fetch=EAGER)
private Collection<Employee> employees ;
...
}
• Relationship can be Loaded Eagerly Can cause Cartesian product
> But if you have several related relationships, could load too much !
OR
• Temporarily override the LAZY fetch type, use Join Fetch in a query:
@NamedQueries({ @NamedQuery(name="getItEarly",
query="SELECT d FROM Department d JOIN FETCH d.employees")})
60
Lazy loading and JPA
• Capture generated SQL
> persistence.xml file:<property name="toplink.logging.level" value="FINE">
• Test run use cases and examine the SQL statements
> optimise the number of SQL statements executed!
> only retrieve the data your application needs!
• Lazy load large (eg BLOB) attributes and relationships that
are not used often
• Override to Eagerly load in use cases where needed
61
Navigating Relationships Detached
Entity
62
Navigating Relationships
Data fetching strategy
• Cascade specifies operations on relationships
> ALL, PERSIST, MERGE, REMOVE, REFRESH
> The default is do nothing
• Avoid MERGE, ALL with deep hierarchies
> If want to do it, limit the scope
63
Using Cascade
@Entity public class Employee {
@Id private int id; Employee
private String firstName;
private String lastName; cascade=ALL
@ManyToOne(cascade=ALL, fetch=LAZY)
private Department dept; Department
}
... X
@Entity public class Department {
@Id private int id; DepartmentCode
private String name;
@OneToMany(mappedBy = "dept"
cascade=ALL, fetch=LAZY)
private Collection<Employee> emps = new ...;
@OneToMany
private Collection<DepartmentCode> codes;
...
}
64
Agenda
>Entity Manager
>Persistence Context
>Entities
>Schema and Queries
>Transaction
65
Database design Basic foundation of performance
• Sm aller tables use less disk, less m em ory, can give better
perform ance
> U se as sm all data types as possible
> use as sm all prim ary key as possible
> Vertical Partition:
●split large, infrequently used colum ns into a separate
onetoone table
• U se good indexing
> Indexes Speed up Q uerys
> Indexes slow dow n U pdates
> Index colum ns frequently used in Q uery W here clause
66
Normalization
67
Mapping Inheritance Hierarchies
Employee
---------------------------
int id
String firstName
String lastName
Department dept
PartTimeEmployee FullTimeEmployee
------------------------ -----------------------
int rate double salary
68
Single Table Per Class
Benefits @Inheritance(strategy=SINGLE_TABLE)
• Simple EMPLOYEE
---------------------------
• No joins required ID Int PK,
FIRSTNAME varchar(255),
> can be fast for Queries LASTNAME varchar(255),
DEPT_ID int FK,
Drawbacks RATE int NULL,
SALARY double NULL,
• Not normalized DISCRIM varchar(30)
69
Joined Subclass
@Inheritance(strategy=JOINED)
Benefits EMPLOYEE
70
Table Per Class
@Inheritance(strategy=TABLE_PER_CLASS)
Benefits
• No need for joins to read
entities of same type PARTTIMEEMPLOYEE
ID int PK,
> Faster reads FIRSTNAME varchar(255),
LASTNAME varchar(255),
Drawbacks DEPT_ID
RATE
int FK,
int NULL
• Not normalized
FULLTIMEEMPLOYEE
> Wastes space
ID int PK,
• Polymorphic queries cause FIRSTNAME
LASTNAME
varchar(255),
varchar(255),
union (all employees) DEPT_ID int FK,
SALARY double NULL
> Poor performance
• This strategy is not mandatory
71
vertical partitioning
C
RE
ATE
T
AB
L
E
C
u
s
to
m
e
r
(
C
,
,
R
E
A
T
u
s
e
r
e
m
a
d
i
s
E
T
A
B
_
i
d
V
A
i
l
p
l
a
y
_
L
I
N
R
n
E
C
u
s
T
N
O
C
H
A
R
(
V
a
m
e
t
o
m
e
r
T
N
U
8
0
)
N
A
R
C
H
A
(
L
A
U
T
O
T
N
U
R
(
5
0
)
O
_
I
N
C
L
N
O
T
R
E
M
E
N
N
U
L
T
IN
T
N
O
T
N
U
L
A
U
T
O_
I
N
C
RE
M
E
N
T
u
,
,
s
e
m
d
i
e
a
s
r
i
p
_
i
d
V
l
l
a
y
A
R
_
n
a
C
H
A
R
(
8
V
A
m
e
0
)
N
R
C
H
A
O
T
N
U
L
R
(
5
0
)
N
O
T
NU
L
Frequently ,
,
p
a
sw
P
R
I
M
o
r
d
A
R
Y
C
K
H
A
R
(
E
Y
(
u
4
1
)
N
s
e
r
_
i
O
T
N
U
d
)
L
,
p
aswo
rd
C
H
A
R
(
41
)
NO
T
N
U
L ,U
N
I
Q
U
E
IND
E
X
(e
m
a
i
l)
,
,
f
i
l
a
r
s
s
t
t
_
n
_
n
a
a
m
e
m
e
V
A
R
C
V
A
R
C
H
H
A
R
(
A
R
(
2
2
5
)
N
O
5
)
N
O
T
T
N
U
L
N
U
L
referenced )E
N
G
I
N
E
I
=no
D
B
;
,
a
dr eV
s A
R
C
H
A
R(
8
0
)
N
O
T
NU
L
,
c
ityV
A
R
CH
A
R
(
3
0)
N
O
T
N
U
L
,
p
rovi
n
ce
C
H
A
R
(
2)
N
OT
N
U
L C
RE
AT
E
TA
B
L
E
C
u
s
t
om
e
r
I
nf
o
(
,
p
ostc
o
de
C
H
A
R
(
7)
N
OT
N
U
L u
s
er
_
i
dI
N
T
N
O
T
N
U
L
,
i
nter
e
st
s
T
E
X
T
N
U
L ,
f
ir
s
t
_n
a
m
e
V
A
R
C
HA
R
(
2
5)
N
O
T
N
U
L
,
b
io T
E
XT
NU
L ,
l
as
t
_
nV
a
m
e
A
R
C
H
AR
(
2
5
)
N
O
T
N
U
L
,
s
igna
t
ur
e
T
E
X
T
N
U
L ,
a
dr
eV
A
R
s C
H
A
R
(8
0
)
NO
T
N
UL
,
s
kils
T
EX
T
N
U
L ,
c
iV
A
t
y
R
C
H
AR
(
3
0
)
N
O
T
N
U
L
,
P
RIMA
R
Y
K
EY
(
u
se
r
_
id
) ,
p
ro
v
i
nC
H
c
e
A
R
(
2
)
N
O
T
N
U
L
,
U
NIQU
E
I
N
DE
X
(
em
a
i
l) ,
p
os
t
c
oC
H
d
e
A
R
(
7
)
N
O
T
N
U
L
)
E
NGIN
E
=I
no
D
B
; ,
i
n
t
er
e
s
t
s
T
E
X
T
N
U
L
,
b
i
oT
E
X
T
N
U
L
,
s
i
g
na
t
u
r
e
T
E
X
T
N
U
L
,
s
k
i
T
E
lsX
T
N
U
L
,
P
R
IM
A
R
Y
K
E
Y
(u
s
e
r
_i
d
)
Less Frequently F
U
LT
,
E
N
G
)
E
X
T
I
N
E
M
=
K
E
Y
y
I
S
A
M
(
i
n
t
e
;
r
e
s
t
s,
s
k
ils
)
referenced,
TEXT data
72
Vertical partitioning
@Entity
public class Customer {
int userid;
String email; @Entity
String password; public class CustomerInfo {
@OneToOne(fetch=LAZY) int userid;
CustomerInfo info; String name;
} String interests;
@OneToOne(mappedBy=
"CustomerInfo")
Customer customer;
}
> split large, infrequently used columns into a separate
table 73
Scalability: Sharding - Application Partitioning
Web/App
Sharding Architecture
Servers
Cust_id 1-999
Cust_id 1000-1999
Cust_id 2000-2999
75
MySQL Query Analyser
76
Agenda
>Entities
>Entity Manager
>Persistence Context
>Queries
>Transaction
77
Query Types – 1
• Named query
> Like findByXXXX() from EntityBeans
> Compiled by persistence engine
> Created either with @NamedQuery or externalized in
orm.xml
• Dynamic query
> Created dynamically by passing a query string to
EntityManager
• Query query = em.createQuery(“select ...”);
> Beware of SQL injection, better to use with named
parameters
q = em.createQuery(“select e from Employee NOT GOOD
e WHERE ”
+ “e.empId LIKE '” + id + “'”);
GOOD
q = em.createQuery(“select e from Employee e WHERE ”
+ “e.empId LIKE ':id'”);
q.setParameter(“id”, id); 78
Query Types – 2
• Native query
> Leverage the native database querying facilities
> Not portable – ties queries to database
79
Flush Mode
• Controls whether the state of managed entities are
synchronized before a query
• Types of flush mode
> AUTO – immediate, default
> COMMIT – flush only when a transaction commits
> NEVER – need to invoke EntityManger.flush() to
flush JTA transaction
//Assume
Order order = em.find(Order.class, orderNumber);
em.persist(order);
Query q = em.createNamedQuery(“findAllOrders”);
q.setParameter(“id”, orderNumber);
q.setFlushMode(FlushModeType.COMMIT);
//Newly added order will NOT visible
List list = q.getResultList();
80
Agenda
>Entities
>Entity Manager
>Persistence Context
>Queries
>Transaction
81
Transactions
• Do not perform expensive and unnecessary
operations that are not part of a transaction
> Hurt performance
> Eg. logging – disk write are expensive, resource contention
on log
• Do not use transaction when browsing data
> Create your own EntityManager as oppose to injected
82
Carol McDonald
Java Architect
83
83